DEV Community

Patrick Lusaya
Patrick Lusaya

Posted on

Create a Laravel CRUD Restful API and Secure it with Sanctum

Introducton
Laravel is a popular PHP framework that allows developers to build web applications quickly and efficiently. One of the most common use cases for Laravel is to create a RESTful API that can be used to communicate with other applications or services. In this tutorial, we'll show you how to build a Laravel CRUD RESTful API and secure it with Sanctum.

Sanctum is a Laravel package that provides a simple way to authenticate API requests using tokens. It works by creating a token for each authenticated user, which can then be used to make subsequent requests to the API. This makes it easy to secure your API and ensure that only authorized users have access to sensitive data. In this tutorial, we'll use Sanctum to secure our Laravel API and protect it from unauthorized access.

Prerequisites
Before we start, make sure you have the following:

  • Laravel installed on your machine
  • A basic understanding of Laravel
  • A code editor of your choice

Step 1: Set Up Laravel
First, we'll create a new Laravel project. Open your terminal or command prompt and enter the following command: composer create-project laravel/laravel my-api

Step 2: Set Up Database
Next, we need to set up our database. In this tutorial i will be using PostgreSql but you can use any database that you are comfortable with. Head over to your database console and create a database by writing the command CREATE DATABASE laravelapi. Open the .env file in the root of your project and update the following lines with your database credentials:

DB_CONNECTION=pgsql //fill according to your database
DB_HOST=127.0.0.1
DB_PORT=5432 // //fill according to your database
DB_DATABASE=laravelapi
DB_USERNAME=yourusername
DB_PASSWORD=yourpassword
Enter fullscreen mode Exit fullscreen mode

Step 4: Create the Model and Migration
In Laravel, a model is a class that represents a database table. It provides an easy way to interact with the database by providing methods for querying and manipulating the table data.

A migration, on the other hand, is a PHP file that defines the changes to be made to the database schema. It provides a simple way to create, update or delete database tables, columns, indexes, and other database-related entities.

So, we will create a Product Model and its migration. The migration file contains the necessary code to create the Product table with the id, name, description, slug ,priceand timestamps columns.

To do this, write the following command on your project root directory: php artisan make:model Product --migration. Now head over to the database/migration folder and open up the file create_products_table. Now inside this file you will notice the up and down functions.

When we run a migration using the php artisan migrate command, Laravel will execute the up function to apply the changes defined in the migration. If we need to undo those changes later, we can run the php artisan migrate:rollback command, which will execute the down function to undo the changes made by the migration.

Inside, the up function add the code:

Schema::create('products', function (Blueprint $table) {
            $table->id();
            $table->string('name');
            $table->string('description')->nullable();
            $table->string('slug');
            $table->decimal('price', 5,2);
            $table->timestamps();
        });
Enter fullscreen mode Exit fullscreen mode

Here's a breakdown of what each line does:

  • Schema::create('products', function (Blueprint $table) starts the process of creating a new database table called products. The function (Blueprint $table) is a callback that defines the columns and their data types.
  • $table->id(); creates an auto-incrementing id column, which serves as the primary key for the products table.
  • $table->string('name'); creates a name column of type string.
  • $table->string('description')->nullable(); creates a description column of type string that can be nullable (i.e. it can have a null value).
  • $table->string('slug'); creates a slug column of type string.
  • $table->decimal('price', 5,2); creates a price column of type decimal with 5 total digits and 2 decimal places.
  • $table->timestamps(); creates two columns, created_at and updated_at, which are used to track the creation and modification timestamps of each row in the products table.

Overall, this code creates a products table with five columns: id, name, description, slug, and price, along with the two timestamp columns.

On Your Product Model class add this code:

protected $fillable = [
    'name',
    'slug',
     'price',
     'description'
    ];
Enter fullscreen mode Exit fullscreen mode

In Laravel, the $fillable property is used to specify which attributes of a model are allowed to be mass-assigned. Mass assignment is a convenient way to create or update multiple model instances at once using an array of data.

By setting these attributes in the $fillable array, we're telling Laravel that it's safe to mass-assign these attributes using an array of data. This is an important security measure that prevents malicious users from assigning arbitrary attributes to our model, which could potentially compromise our application.

Step 5: Create the Controller
Next, let's create a controller to handle our API requests. The primary responsibility of a controller is to receive requests from the client and perform the necessary actions to generate a response. This includes retrieving data from the database, processing data, and rendering views. Run the following command to create a controller : php artisan make:controller ProductController

Step 6: Add API Methods to the Controller
Now, let's add the methods to the controller that will handle the CRUD operations for our API. Open the ProductController.php file and add the following code:

* @return \Illuminate\Http\Response
     */
    public function index()
    {
        return Product::all();
    }
Enter fullscreen mode Exit fullscreen mode

This index function uses the predefined method all() onto the Product Model to find all products

Then, add this code:

* @param  \Illuminate\Http\Request  $request
     * @return \Illuminate\Http\Response
     */
    public function store(Request $request)
    {

        $request -> validate(
           [ 'name' => 'required',  'slug' => 'required',
             'description' => 'required' ,  'price' => 
             'required'
           ]
        );
        return Products::create($request->all());
    }
Enter fullscreen mode Exit fullscreen mode

Here, the store method provides a way to create a new product in the database based on input data from the client, while also validating the input data to ensure that it meets certain requirements.

public function store(Request $request): This is the method definition. The public keyword indicates that the method can be accessed from outside the class, and store is the name of the method. The $request parameter is an instance of the Request class, which contains data from the HTTP request.

$request -> validate([ 'name' => 'required', 'slug' => 'required', 'description' => 'required' , 'price' => 'required' ]);: This line uses the validate method on the $request object to validate the input data from the client. In this case, the validation rules require the name, slug, description, and price fields to be present and not empty.

return Products::create($request->all());: This line creates a new product in the database using the create method on the Products model. The create method creates a new instance of the Products model using the input data from the client, and saves it to the database. The all() method on the $request object returns an array of all input data from the client.

After that, add the following code to find Product by Id, Delete Product by ID and Update Product by ID:

* @param  int  $id
     * @return \Illuminate\Http\Response
     */
    public function show($id)
    {
        return Products::find($id);
    }

    /**
     * Update the specified resource in storage.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  int  $id
     * @return \Illuminate\Http\Response
     */
    public function update(Request $request, $id)

    {
        $product = Products::find($id);
        $product->update($request->all());
        return $product;
    }

    /**
     * Remove the specified resource from storage.
     *
     * @param  int  $id
     * @return \Illuminate\Http\Response
     */
    public function destroy($id)
    {
       return Products::destroy($id);
    }
Enter fullscreen mode Exit fullscreen mode

The show function retrieves a product by its ID, the update function updates a product by its ID, and the destroy function deletes a product by its ID. Each of these functions returns an HTTP response that can be handled by a client-side application or another API.

Step 6: Create the public API Routes
Now, let's create the routes for our API. Open the routes/api.php file and add the following code:

//these are public routes
Route:: get('/products',[ProductController::class,'index']);
Route:: get('/products/{id}',[ProductController::class,'show']);
Enter fullscreen mode Exit fullscreen mode

The first line defines a GET route for the indexmethod of the ProductController class. This route is accessible at the "/products" endpoint and will return a list of all products.

The second line defines a GET route for the "show" method of the ProductController class that expects an "id" parameter. This route is accessible at the "/products/{id}" endpoint and will return a single product with the specified ID.

Now, to secure some of our endpoint we will use Sanctum. We want that for a user to create, update and delete a product he must be authenticated. Before anything else lets do a quick setup of Sanctum in your project.

  • Install laravel Sanctum via composer using the command: composer require laravel/sanctum
  • Next, you should publish the Sanctum configuration and migration files using the command: php artisan vendor:publish --provider="Laravel\Sanctum\SanctumServiceProvider". This will create a table create_accesss_tokens in your database.
  • Migrate using the command php artisan migrate.
  • Now,you should add Sanctum's middleware to your api middleware group within your application's app/Http/Kernel.php file: Within the middlewareGroups array replace the second element of the array with following code :
 'api' => [
            \Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class,
            \Illuminate\Routing\Middleware\ThrottleRequests::class.':api',
           \Illuminate\Routing\Middleware\SubstituteBindings::class,
         ],
Enter fullscreen mode Exit fullscreen mode

Step 7: Create User Model and Migration
We need to have users who can be authenticated before accessing the secure endpoints. On your project's root directory run the command: php artisan make:model User --migration

Head over the migration folder, open the create_users_table file, and within its up function paste the following code:

Schema::create('users', function (Blueprint $table) {
            $table->id();
            $table->string('name');
            $table->string('email')->unique();
            $table->timestamp('email_verified_at')->nullable();
            $table->string('password');
            $table->rememberToken();
            $table->timestamps();
        });
Enter fullscreen mode Exit fullscreen mode

This will create a users table with columns id, name, email, password, tokens and timestamps.
Now within the User Model class add :

protected $fillable = [
        'name',
        'email',
        'password',
    ];
Enter fullscreen mode Exit fullscreen mode

Then run the command php artisan migrate to create the users table in your database.

Step 8: Create AuthController
The Authcontroller will handle the login, register and logout logic.

  • Register logic To register user add the following code in your controller:
public function register(Request $request)
    {
$fields= $request->validate(
    [
        'name'=> 'required |string',
        'email' => 'required |unique:users',
        'password'=> 'required|confirmed'
    ]
    );
    $user = User::create([
        'name'=>$fields['name'],
        'email'=>$fields['email'],
        'password'=> bcrypt($fields['password'])
    ]);
    $token = $user->createToken('mytoken')->plainTextToken;
    $response =  [
        'user'=> $user,
        'token'=>$token

    ];
    return response($response, 201);

    }

Enter fullscreen mode Exit fullscreen mode

Code Explanation
The register function accepts a HTTP request object as a parameter, which is used to validate the user's input fields. The method then creates a new user in the database with the validated input fields and hashes the user's password using the bcrypt function.

The user is then assigned a token using Laravel's built-in Sanctum package, which is returned along with the user's details in a response object with a status code of 201.

The createToken method is called on the authenticated user object and takes a string argument that serves as a name for the token. In this case, the name is mytoken.

The plainTextToken method is then called on the returned token object to retrieve the plain text version of the token. This token can be used for subsequent requests that require user authentication.

  • Login Logic This almost the same as register only that some few things differ. Add the following code to your AuthController:
public function login(Request $request)
    {
$fields= $request->validate(
    [

        'email' => 'required ',
        'password'=> 'required'
    ]
    );
    $user = User::where('email', $fields['email'])->first();

    if (!$user|| !Hash::check($fields['password'], $user->password)) {

        return response(
            [
                'message' => 'Bad creds'
            ]
            );
        # code...
    }
    $token = $user->createToken('mytoken')->plainTextToken;
    $response =  [
        'user'=> $user,
        'token'=>$token

    ];
    return response($response, 201);

    }

Enter fullscreen mode Exit fullscreen mode

Code Explanation
The function It takes in a POST request with the email and password fields validated. It then checks if a user with the provided email exists in the database and whether the password provided matches the hashed password in the database using the Hash facade provided by Laravel.

If the user is not found or the password doesn't match, it returns a response with a message indicating bad credentials. If the user is found and the password matches, it generates a token for the user and returns a response with the user details and the token. The token can be used to authenticate subsequent requests to the server.

  • Logout Logic Add:
public function logout(Request $request){
        $request->user()->token()->delete();
       return [
        'message' => 'logged out'
       ];
    }
Enter fullscreen mode Exit fullscreen mode

This retrieves the authenticated user using $request->user() and then deletes the user's token using the delete() method

Step 9: Created the protected API Routes
A user must be authenticated to access these routes. Add the following code to routes.api.php file:

// protected routes
Route::group(['middleware'=> ['auth:sanctum']], function(){
    Route::post('/products',[ProductController::class,'store']);
    Route::put('/products/{id}',[ProductController::class,'update']);
    Route::delete('/products/{id}',[ProductController::class,'destroy']);
    Route::post('/logout',[AuthController::class,'logout']);

});
Enter fullscreen mode Exit fullscreen mode

Route::group() is a function that allows you to group multiple routes that share a common middleware. In this case, the middleware specified is auth:sanctum, which is used to protect the routes in the group and restrict access to authenticated users only.

When a user tries to access any route within the group, the auth:sanctum middleware will check if the user is authenticated by checking for the presence of a valid access token. If the user is not authenticated, they will be redirected to the login page or will receive an HTTP 401 Unauthorized status code.

By wrapping a set of routes within a Route::group()function with the specified middleware, it ensures that those routes are only accessible to authenticated users who have a valid access token.

Step 10: Test your endpoints with postman
Head over to Postman and test your endpoints. But first start your server by using the command php artisan serve

Since, there are no products in our database, lets first register a user to do so.
Use the endpoint http://localhost:8000/api/register

Post request to register user

Response

To Login use the endpoint http://localhost:8000/api/login

Image description

The response will have an authentication token that will be used to access protected routes.

Image description

Access protected routes after Authentication

Image description

Access protected routes without Authentication

Image description

Up to that point you can perform all the CRUD operations using the RESTful API you just created .

Here is a complete code of all we have done

Product Model

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class Product extends Model
{
    use HasFactory;
    protected $fillable = [
    'name',
    'slug',
     'price',
     'description'
    ];
}

Enter fullscreen mode Exit fullscreen mode

User Model

<?php

namespace App\Models;

// use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Laravel\Sanctum\HasApiTokens;

class User extends Authenticatable
{
    use HasApiTokens, HasFactory, Notifiable;

    /**
     * The attributes that are mass assignable.
     *
     * @var array<int, string>
     */
    protected $fillable = [
        'name',
        'email',
        'password',
    ];

    /**
     * The attributes that should be hidden for serialization.
     *
     * @var array<int, string>
     */
    protected $hidden = [
        'password',
        'remember_token',
    ];

    /**
     * The attributes that should be cast.
     *
     * @var array<string, string>
     */
    protected $casts = [
        'email_verified_at' => 'datetime',
    ];
}

Enter fullscreen mode Exit fullscreen mode

ProductController

<?php

namespace App\Http\Controllers;

use App\Models\Products;
use Illuminate\Http\Request;

class ProductController extends Controller
{
    /**
     * Display a listing of the resource.
     *
     * @return \Illuminate\Http\Response
     */
    public function index()
    {
        return Products::all();
    }

    /**
     * Store a newly created resource in storage.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return \Illuminate\Http\Response
     */
    public function store(Request $request)
    {

        $request -> validate(
           [ 'name' => 'required',  'slug' => 'required',
           'description' => 'required' ,  'price' => 'required'
           ]
        );
        return Products::create($request->all());
    }

    /**
     * Display the specified resource.
     *
     * @param  int  $id
     * @return \Illuminate\Http\Response
     */
    public function show($id)
    {
        return Products::find($id);
    }

    /**
     * Update the specified resource in storage.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  int  $id
     * @return \Illuminate\Http\Response
     */
    public function update(Request $request, $id)

    {
        $product = Products::find($id);
        $product->update($request->all());
        return $product;
    }

    /**
     * Remove the specified resource from storage.
     *
     * @param  int  $id
     * @return \Illuminate\Http\Response
     */
    public function destroy($id)
    {
       return Products::destroy($id);
    }

}

Enter fullscreen mode Exit fullscreen mode

AuthController

<?php

namespace App\Http\Controllers;

use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Hash;

class AuthController extends Controller
{
    public function register(Request $request)
    {
$fields= $request->validate(
    [
        'name'=> 'required |string',
        'email' => 'required |unique:users',
        'password'=> 'required|confirmed'
    ]
    );
    $user = User::create([
        'name'=>$fields['name'],
        'email'=>$fields['email'],
        'password'=> bcrypt($fields['password'])
    ]);
    $token = $user->createToken('mytoken')->plainTextToken;
    $response =  [
        'user'=> $user,
        'token'=>$token

    ];
    return response($response, 201);

    }

    public function login(Request $request)
    {
$fields= $request->validate(
    [

        'email' => 'required ',
        'password'=> 'required'
    ]
    );
    $user = User::where('email', $fields['email'])->first();

    if (!$user|| !Hash::check($fields['password'], $user->password)) {

        return response(
            [
                'message' => 'Bad creds'
            ]
            );
        # code...
    }
    $token = $user->createToken('mytoken')->plainTextToken;
    $response =  [
        'user'=> $user,
        'token'=>$token

    ];
    return response($response, 201);

    }

    public function logout(Request $request){
        $request->user()->token()->delete();
        return response(null, 204);
    }
}


Enter fullscreen mode Exit fullscreen mode

api.php

<?php

use App\Http\Controllers\AuthController;
use App\Http\Controllers\ProductController;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;
use Laravel\Sanctum\Sanctum;

/*
|--------------------------------------------------------------------------
| API Routes
|--------------------------------------------------------------------------
|
| Here is where you can register API routes for your application. These
| routes are loaded by the RouteServiceProvider within a group which
| is assigned the "api" middleware group. Enjoy building your API!
|
*/


// Route::resource('products', ProductController::class);

// public routes
Route:: post('/register',[AuthController::class,'register']);
Route:: get('/products',[ProductController::class,'index']);
Route:: get('/products/{id}',[ProductController::class,'show']);
Route:: post('/login',[AuthController::class,'login']);

// protected routes
Route::group(['middleware'=> ['auth:sanctum']], function(){
    Route::post('/products',[ProductController::class,'store']);
    Route::put('/products/{id}',[ProductController::class,'update']);
    Route::delete('/products/{id}',[ProductController::class,'destroy']);
    Route::post('/logout',[AuthController::class,'logout']);

});
Route::middleware('auth:sanctum')->get('/user', function (Request $request) {
    return $request->user();
});

Enter fullscreen mode Exit fullscreen mode

Conclusion
In this tutorial, we covered the basics of building a RESTful API with Laravel and Sanctum. We started by setting up our Laravel project and database, creating migration files, and defining our models and controllers. We also discussed how to handle requests and responses, including creating, retrieving, updating, and deleting resources. We explored the use of middleware for authentication and authorization, and how to create and use access tokens with Sanctum.

Overall, this tutorial provided a solid foundation for building a robust and secure API with Laravel and Sanctum.

That's all, Enjoy!

Top comments (0)