API Development with Laravel 10 – Build a REST API for a Bookstore

API Development with Laravel 10: Build a REST API for a Bookstore

API Development with Laravel 10 – Build a REST API for a Bookstore

Step 1: Setting Up the Environment

Let’s get started building! Here’s a step-by-step guide on installing Laravel 10, setting up the database, and creating the basic CRUD operations for our Bookstore API.

Prerequisites:

  • PHP: Ensure you have PHP version 8.1 or above installed on your system. You can check your version on the command line with php -v.
  • Composer: Composer is the dependency manager for PHP. You can download and install it from https://getcomposer.org/  You can check the version using this command line composer -v

Steps:

  1. Install Laravel 10:
    • Open your terminal or command prompt.
    • Navigate to the directory where you want to create your project (e.g., cd Documents)
    • Run the following command to create a new Laravel project:
composer create-project laravel/laravel bookstore-api

composer create-project laravel/laravel mybookstore "10.*"

What is a Virtual Host?

  • A virtual host is a configuration that allows a single web server (like Apache) to host multiple websites or applications.
  • Each virtual host has its own:
    • DocumentRoot: The directory where the project’s files are located.
    • ServerName: A domain name or alias that points to the specific project.
    • Error Logs: Separate error and access logs for each project

How To Create a Virtual Host

The process depends on your web server (Apache, Nginx, etc.) and operating system. Here’s a general outline for Apache:

  1. Create New Virtual Host Configuration File:

Open your main Apache configuration file (httpd.conf), located in your XAMPP installation directory (e.g., C:\xampp\apache\conf\httpd.conf).

    • Find the line that looks approximately like this:

# Virtual hosts

Include conf/extra/httpd-vhosts.conf

    • Paste the following template and modify accordingly:
  1. # Virtual hosts
    
    Include conf/extra/httpd-vhosts.conf
    
    <VirtualHost *:80>
    
        DocumentRoot "/path/to/your/project/public"
    
        ServerName yourproject.local
    
        <Directory "/path/to/your/project/public">
    
            Options Indexes FollowSymLinks
    
            AllowOverride All
    
            Require all granted
    
        </Directory>
    
        ErrorLog "logs/yourproject-error.log"
    
        CustomLog "logs/yourproject-access.log" common
    
    </VirtualHost>

    Update Your hosts File:

    • Add an entry to map yourproject.local to your local IP address (127.0.0.1). You’ll find this file at:
      1. Windows: C:\Windows\System32\drivers\etc\hosts
      2. Linux/macOS: /etc/hosts

On your Laravel app update your app_url in the .env file like this

APP_URL=http://bookstore-api.local

  1. Restart Apache:
    • Click “Stop” next to Apache, and then click “Start” to restart the web server on windows
    • Use the appropriate command for your system, e.g., sudo service apache2 restart.

Run the app using this url: http://bookstore-api.local/

  1. Set Up Database:
  • Create Database: Create a database named ‘bookstore’ in your preferred database management system (MySQL, PostgreSQL, etc.).
  • Update Environment File:
    • Go to the root of your project folder (bookstore-api) and find the .env file.
    • Update the following lines with your database connection details:
DB_CONNECTION=mysql

DB_HOST=127.0.0.1

DB_PORT=3306

DB_DATABASE=bookstore

DB_USERNAME=your_database_username

DB_PASSWORD=your_database_password
  1. Create a Model and Migration:
  • Run the following command to create a Book model and its corresponding migration:
php artisan make:model Book -m
  • Open the newly created migration file within the database/migrations folder. Define the schema for your books table:
public function up(): void
    {
        Schema::create('books', function (Blueprint $table) {
            $table->id();
            $table->string('title');
            $table->string('author');
            $table->string('isbn')->unique();
            $table->decimal('price', 8, 2);
            $table->timestamps();
        });
    }

Run the migration:

php artisan migrate

Layout of The Project

  • Before go to step two let me share with you the layout of the project are creating.
  • Since we’re focused purely on building an API (no fancy visual frontend this time), the ‘layout’ will primarily be in the form of a conceptual diagram and a description of the data flow.

Conceptual Diagram

[User/Client] --(HTTP Requests)--> [Laravel API (Bookstore)] --(Database Queries)--> [Bookstore Database]

Explanation

  • User/Client: This represents any application that is going to use your API. It could be a website, a mobile app, or even another API. For this tutorial, we’ll use Postman as the ‘client’ to make requests.
  • HTTP Requests: The client will communicate with our Laravel API using standard HTTP requests:
    • GET (to retrieve data)
    • POST (to create new data)
    • PUT (to update data)
    • DELETE (to delete data)
  • Laravel API (Bookstore): This is the heart of our project, the code we’ll write in Laravel:
    • Routes will define URLs the client can access.
    • Controllers will handle requests and interact with the database.
  • Bookstore Database: A database to store and manage the book data (like title, author, ISBN, price).

Data Flow Example (GET Request)

  1. A user wants to see a list of books. Their frontend application (or Postman) will send a GET request to /api/books
  2. Laravel API routes this request to the BookController.
  3. The BookController queries the ‘books’ database table to fetch all book records.
  4. The database returns the data.
  5. The BookController formats the data as JSON.
  6. The Laravel API sends the JSON response back to the client.
  7. The client renders the data for the user to see.

Step 2: Create the Controller to handle the API logic and Set up the routes

Let’s create the controller and set up the API routes for our Bookstore.

  1. Create the Controller
  • Use the artisan command to generate an API controller:
php artisan make:controller Api/BookController --resource
  • The –resource flag automatically generates the methods we need for basic CRUD
  1. Update the Controller
    • Open the generated controller file (app/Http/Controllers/Api/BookController.php).
  • Update the controller code as follows:
<?php

namespace App\Http\Controllers\Api;

use App\Models\Book;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;

class BookController extends Controller
{
    /**
     * Display a listing of the resource.
     */
    public function index()
    {
        return Book::all(); // Get all books
    }

    /**
     * Show the form for creating a new resource.
     */
    public function create()
    {
        //
    }

    /**
     * Store a newly created resource in storage.
     */
    public function store(Request $request)
    {
        $book = Book::create($request->all()); // Create a new book
        return response()->json($book, 201); 
    }

    /**
     * Display the specified resource.
     */
    public function show(string $id)
    {
        $book = Book::find($id);
        if (!$book) { // Check if the book is found
            return response()->json(['message' => 'Book not found'], 404);
        }
        return $book;
    }

    /**
     * Show the form for editing the specified resource.
     */
    public function edit(string $id)
    {
        //
    }

    /**
     * Update the specified resource in storage.
     */
    public function update(Request $request, string $id)
    {
        $book = Book::find($id);
        if (!$book) {
            return response()->json(['message' => 'Book not found'], 404);
        }  
        $book->update($request->all());
        return response()->json($book, 200);
    }

    /**
     * Remove the specified resource from storage.
     */
    public function destroy(string $id)
    {
        $book = Book::find($id);
        if (!$book) {
            return response()->json(['message' => 'Book not found'], 404);
        } 
        $book->delete();
        return response()->json(['message' => 'Book deleted'], 200); 
    }
}
  1. Set up the routes:
  • Open the routes/api.php file.
  • Add the following line:
Route::resource('books', BookController::class)->except(['create', 'edit']);

Note: The resource method typically includes create and edit routes, however, these are commonly used for building front-facing forms within a web application. In a pure API context, it’s up to you whether you want to exclude them:

Use Postman:

  • Make a GET request to http://bookstore-api.local/api/books.
  • You should receive an empty array ([]) since your database is empty.

Explanation:

Route::resource(‘books’, BookController::class): This line automatically generates the following routes:

| Method | URI               | Action  | Route Name  |

| ------ | ----------------- | -------- | ------------ |

| GET    | /books            | index    | books.index  |

| GET    | /books/create     | create   | books.create |

| POST   | /books            | store    | books.store  |

| GET    | /books/{book}     | show     | books.show   |

| GET    | /books/{book}/edit| edit     | books.edit   |

| PUT    | /books/{book}     | update   | books.update |

| DELETE | /books/{book}     | destroy  | books.destroy|

Step 3: Let’s Test Our Bookstore API

Let’s move on to testing our Bookstore API with Postman! Here’s a step-by-step guide:

  1. Setting up Postman:
  • Download: If you haven’t already, download and install Postman from https://www.postman.com/.
  • Workspace: Create a new workspace in Postman and give it a descriptive name (e.g., “Bookstore API Testing”).
  1. Testing the Endpoints

GET /api/books (Retrieve all books):

    • Method: In Postman, set the request type to GET.
    • URL: Enter the URL: http://bookstore-api.local/api/books (or the URL where your Laravel app is running).
    • Send: Click the “Send” button. You should see an empty array response ([]) since you don’t have any books in your database yet.

POST /api/books (Create a new book):

    • Method: Select POST.
    • URL: http://bookstore-api.local/api/books
    • Body: Select the “Body” tab, then the “raw” option, and select “JSON” from the dropdown.
    • Enter JSON Data: Paste in JSON data for a book:
{
    "title": "The Hitchhiker's Guide to the Galaxy",
    "author": "Douglas Adams",
    "isbn": "978-0345391803",
    "price": 12.99
}
  • Send: Click the “Send” button. You should see the newly created book in the response with a status code of 201 (Created).

GET /api/books/{id} (Get a single Book)

    • Get the id of the book you just created from the previous response.
    • Change the request method to GET.
    • URL: http://bookstore-api.local/api/books/{id} (replace {id} with the actual ID).
    • Send: Click the “Send” button. You should see the details of the book in the response.

(Modify the book data as needed and test…)

PUT /api/books/{id} (Update a book)

  1. Get the ID:Retrieve the id of an existing book from your database (either from a previous GET request or by directly looking at the database).
  2. Method:Select the PUT
  3. URL:http://bookstore-api.local/api/books/{id} (replace {id} with the actual book ID).
  4. Body:
    • Select the “Body” tab and ensure “raw” and “JSON” are selected just as you did for the POST request.
    • Paste in a JSON object representing the updated book data. Modify only the fields you want to change. Example:
{
    "title": "The Hitchhiker's Guide to the Galaxy (Updated Edition)",
    "price": 13.99
}
  1. Send: Hit the “Send” button.
    • You should receive a response with the updated book data and a status code of 200 (OK).

DELETE /api/books/{id} (Delete a book)

  1. Get the ID: Retrieve the id of a book you wish to delete.
  2. Method: Change the request method to DELETE.
  3. URL: http://bookstore-api.local/api/books/{id} (replace {id} with the actual book ID).
  4. Body: You don’t need a body for a DELETE request.
  5. Send: Click the “Send” button.
    • You should get a 200 (OK) status code, and a success message in the response (like the one you defined in your controller).

Authentication and Authorization

  1. Authentication (Using Laravel Sanctum)
  • Laravel Sanctum is a fantastic solution for API authentication. Here’s a step-by-step guide on how to implement this:

Step 1: Install and Configure Laravel Sanctum

1.Install Sanctum:

composer require laravel/sanctum

2.Publish Sanctum’s configuration:

php artisan vendor:publish --provider="Laravel\Sanctum\SanctumServiceProvider"

3.Run database migrations:

php artisan migrate

Step 2: Modify your User Model

  1. Add the necessary trait:
Import the Laravel\Sanctum\HasApiTokens trait into your User Model.
use Laravel\Sanctum\HasApiTokens;
class User extends Authenticatable
{
    use HasApiTokens, HasFactory, Notifiable;
    // ... other user model code
}

Add the ‘role’ Column:

  • Generate a migration:
php artisan make:migration add_role_column_to_users_table

Edit the migration file: Open the newly created migration file within your database/migrations directory. Add the following code within the up method:

public function up()
{
    Schema::table('users', function (Blueprint $table) {
        $table->string('role')->default('user'); // Set the default role as 'user'
    });
}

 

Run the migration:

php artisan migrate

Also update the User model by adding a role field in the protected fillable array.

protected $fillable = [
        'name',
        'email',
        'password',
        'role',
    ];

Step 3: Create API Routes for Authentication

  • We’ll need routes for login, registration and logout. You can put these in either your routes/api.php file or create a separate file. Here’s an example:
Route::prefix('auth')->group(function () {

    Route::post('/register', [AuthController::class, 'register']);  

    Route::post('/login', [AuthController::class, 'login']);

    Route::post('/logout', [AuthController::class, 'logout'])->middleware('auth:sanctum'); // Protected route

});

 

Step 4: Create an AuthController

  1. Generate the controller:
php artisan make:controller Api/AuthController

Implement methods: Add register, login, and logout methods inside your AuthController. You’ll need basic user creation logic and Sanctum’s token creation features in these methods.

<?php

namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use App\Models\User;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Validator;

class AuthController extends Controller
{
    public function register(Request $request)
    {

        // Basic validation of incoming data
        $validator = Validator::make($request->all(), [
            'name' => 'required|string|max:255',
            'email' => 'required|string|email|max:255|unique:users',
            'password' => 'required|string|min:8',
            'role' => 'required|string|in:admin,user',  // Ensure the 'role' field is included and valid
        ]);
        if ($validator->fails()) {
            return response()->json(['error' => $validator->errors()], 422); // Return validation errors
        }

        // Create a new User object and save it to the database
        $user = User::create([
            'name' => $request->name,
            'email' => $request->email,
            'password' => Hash::make($request->password), // Hash the password for security
            'role' => $request->role
        ]);


        // Generate an API token for the newly registered user
        return response()->json([
            'token' => $user->createToken('auth-token')->plainTextToken,  
            'message' => 'User registered successfully'
        ], 201); // 201 Created status code
    }


    public function login(Request $request)
    {
        // Validate the email and password provided in the request
        $validator = Validator::make($request->all(), [
            'email' => 'required|email',
            'password' => 'required|string'
        ]);

        if ($validator->fails()) {
            return response()->json(['error' => $validator->errors()], 422); // Return validation errors
        }
        // Find the user by their email address
        $user = User::where('email', $request->email)->first();
        // Check if a user was found and the password matches
        if (!$user || !Hash::check($request->password, $user->password)) {
            return response()->json(['error' => 'Invalid credentials'], 401); // 401 Unauthorized status
        }

        // Create an API token for the user
        return response()->json([
            'token' => $user->createToken('auth-token')->plainTextToken,
            'message' => 'User logged in successfully'
        ]);
    }

    public function logout(Request $request)
    {
        // Delete the current access token for the authenticated user
        $request->user()->currentAccessToken()->delete();
        return response()->json(['message' => 'User logged out'], 200); // 200 Success status code
    }
}

if (!$user || !Hash::check($request->password, $user->password)) {
            return response()->json(['error' => 'Invalid credentials'], 401); // 401 Unauthorized status
        }

This part of the code means

  • “If there’s no user OR if the password check fails…”.
  • !Hash::check($request->password, $user->password): Here, the ! negates the result of the Hash::check function. Hash::check compares the plain text password provided in the request with the hashed password stored in the database. If they don’t match, it returns false, and the ! turns that into true
  1. Authorization
  2. Generating the Middleware

Use Laravel’s artisan command to generate the middleware:

php artisan make:middleware CheckRole

This will create a file named CheckRole.php inside your app/Http/Middleware directory.

  1. Implementing the Middleware Logic
  • Open the newly created CheckRole.php file.
  public function handle(Request $request, Closure $next, ...$roles)
    {
        // Check if the authenticated user's role is within the allowed roles
        if (!in_array($request->user()->role, $roles)) {  
            // User's role is not authorized, return an error response
            return response()->json(['error' => 'Unauthorized'], 403);  
        }
        // User's role is authorized, proceed to the next middleware or controller
        return $next($request);

    }

 

  1. Registering the Middleware

To make Laravel aware of your middleware, you need to register it.

  • Open the app/Http/Kernel.php file.
  • Locate the $routeMiddleware array. Add the following line within that array:
'CheckRole' => \App\Http\Middleware\CheckRole::class,

Explanation of Middleware Code

  • handle method: This is the core of the middleware. It intercepts the incoming request before it hits your controller.
  • …$roles: Using the splat operator (…) allows our middleware to accept multiple roles.
  • in_array($request->user()->role, $roles): Checks if the authenticated user’s role exists within the allowed roles.
  • return $next($request);: Allows the request to proceed to the controller if authorization is successful.

Finally update your routes/api as follows…

// Authentication routes

Route::prefix('auth')->group(function () {
    Route::post('/register', [AuthController::class, 'register']);  
    Route::post('/login', [AuthController::class, 'login']);
    Route::post('/logout', [AuthController::class, 'logout'])->middleware('auth:sanctum'); // Protected route
});

// Global authentication for API routes
Route::middleware('auth:sanctum')->group(function () {
    Route::get('/user', function (Request $request) {
        return $request->user();
    });


    // Other API routes requiring general authentication go here...
    // Admin-only routes
    Route::middleware('CheckRole:admin')->group(function() {
    // Routes accessible only by admins (e.g., POST /books, PUT /books/{id}, DELETE /books/{id} )
    // Bookstore API routes
    Route::resource('books', BookController::class)->except(['create', 'edit']);
    });
});

 

Let’s break down the key differences between the auth:sanctum and CheckRole:admin middlewares in your route group:

auth:sanctum

  • Type: Authentication Middleware
  • Purpose: Acts as a gatekeeper, ensuring that ONLY authenticated users can access any routes within its group.
  • How it Works:
    • Looks for a valid Sanctum API token (usually in the Authorization header).
    • Verifies the token against your user database.
    • If no token or an invalid token is provided, it rejects the request with a 401 Unauthorized error.

CheckRole:admin

  • Type: Authorization Middleware (custom-made)
  • Purpose: Provides finer-grained control on top of authentication, specifically checking if the user has the ‘admin’ role.
  • How it Works:
    • Assumes the user is already authenticated (this middleware is nested within the auth:sanctum group).
    • Retrieves the authenticated user’s ‘role’ property.
    • Denies access with a 403 Forbidden error if the user is not an ‘admin’.

In Summary

Middleware Type Focus
auth:sanctum Authentication Is there a valid logged-in user?
CheckRole:admin Authorization Does the user have the right level of access?

 

Testing Plan:

  1. Unauthenticated Access (GET /api/books):
  • Method: GET
  • Headers: None
  • Expected Response: 401 Unauthorized
  1. User Authentication:
  2. Register a new user (POST /api/auth/register):
  • Method: POST
  • Body (JSON): { “name”: “Test User”, “email”: “testuser@example.com”, “password”: “testpassword”, “role”: “user” }
  • Expected Response: 201 Created with an API token
  1. Login as the user (POST /api/auth/login):
  • Method: POST
  • Body (JSON): { “email”: “testuser@example.com”, “password”: “testpassword” }
  • Expected Response: 200 OK with an API token
  1. Standard User Access:
  2. Access protected route (GET /api/books):
  • Method: GET
  • Headers:
    • Key: Authorization
    • Value: Bearer <your_api_token> (from login response)
  • Expected Response: 403 Forbidden (not authorized for user)
  1. Attempt to create a book (POST /api/books):

Creating new book record

{
    "title": "The Hitchhiker's Guide to the Galaxy",
    "author": "Douglas Adams",
    "isbn": "978-0345391803",
    "price": 12.99
}

Method: POST

  • Headers: Include Authorization header
  • Body (JSON): Valid book data
  • Expected Response: 403 Forbidden (not authorized for user)
  1. Admin User Access (Repeat steps 2a and 2b with “role”: “admin” during registration):
{
    "name": "Admin User",
    "email": "admin@example.com",
    "password": "password",
    "role": "admin"
}
{
     “email”:admin@example.com”,
     “password”:”password”
}
  • Use admin token to create a book (POST /api/books)

Creating new book record

{
"title": "The Hitchhiker's Guide to the Galaxy",
"author": "Douglas Adams",
"isbn": "978-0345391803",
"price": 12.99
}
  • Expected Response: 201 Created (successful creation)

Watch The YouTube Video Here:

 

 

 

 

 

 

Sign up for free tutorials in your inbox.

We don’t spam! Read our privacy policy for more info.

Leave a Comment

Your email address will not be published. Required fields are marked *