blog post
Building a Movie Database RESTful API with Laravel: A Step-by-Step Guide
By Luis Velazquez Bilbao • Feb 1, 2025
Table of Contents
Introduction

Hello, fellow developers! Today, I'm excited to share my experience building a robust Movie Database API using Laravel. This project showcases the power of Laravel for creating RESTful APIs and demonstrates best practices in API development, testing, and authentication.

Project Overview

The goal was to create an API that allows users to manage a movie database. The main features include:

  • Creating new movies
  • Retrieving movie details
  • Updating existing movies
  • Deleting movies
  • User authentication and authorization
  • Manage user's "Watch Later" list
Setting Up the Project

I started by setting up a new Laravel project and configuring the database. Laravel's built-in tools made this process smooth and straightforward.

    
ssh
Copy
composer create-project laravel/laravel movie-database-api cd movie-database-api php artisan migrate
Database Design

The database structure includes the following main tables:

  • users for storing user information
  • movies for storing movie details
  • genres for categorizing movies
  • movie_user for managing "watch later" list

Here's an example of the migration for the movies table:

    
php
Copy
public function up() { Schema::create('movies', function (Blueprint $table) { $table->id(); $table->string('title'); $table->text('description'); $table->date('release_date'); $table->foreignId('genre_id')->constrained(); $table->timestamps(); }); }
Model Creation

I created Eloquent models for each of the main entities. Here's an example of the Movie model:

    
php
Copy
// app/Models/Movie.php namespace App\Models; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; class Movie extends Model { use HasFactory; protected $fillable = ['title', 'description', 'release_date', 'genre_id']; public function genre() { return $this->belongsTo(Genre::class); } }
API Development

The API endpoints were created using Laravel's resource controllers. Here's an example of the store method in the MovieController :

    
php
Copy
// app/Http/Controllers/MovieController.php public function store(Request $request) { $validatedData = $request->validate([ 'title' => 'required|string|max:255', 'description' => 'required|string', 'release_date' => 'required|date', 'genre_id' => 'required|exists:genres,id', ]); $movie = Movie::create($validatedData); return response()->json($movie, 201); }
Route Configuration

I defined the API routes in the routes/api.php file. Here's a snippet of the route configuration:

    
php
Copy
// routes/api.php use App\Http\Controllers\MovieController; Route::middleware('auth:sanctum')->group(function () { Route::apiResource('movies', MovieController::class); Route::post('movies/{movie}/watch-later', [MovieController::class, 'addToWatchLater']); Route::delete('movies/{movie}/watch-later', [MovieController::class, 'removeFromWatchLater']); Route::get('watch-later', [MovieController::class, 'getWatchLaterList']); });
Endpoints

Here's a breakdown of the main API endpoints:

Authentication Endpoints:

  • POST /register
    • Description: Register a new user
    • Request Body: JSON object with user details (name, email, password)
    • Response: JSON object with user details and access token
    • Status Code: 201 Created
  • POST /login
    • Description: Authenticate a user and get an access token
    • Request Body: JSON object with email and password
    • Response: JSON object with user details and access token
    • Status Code: 200 OK or 401 Unauthorized
  • POST /logout
    • Description: Logout the authenticated user (revoke the token)
    • Response: JSON object with logout confirmation
    • Status Code: 200 OK
    • Authentication: Requires a valid access token

Movie Endpoints:

  • GET /movies
    • Description: Retrieve all movies
    • Response: JSON array of movie objects
    • Authentication: Requires a valid access token
    • Status Code: 200 OK
  • POST /movies
    • Description: Create a new movie
    • Request Body: JSON object with movie details (title, description, release_date, genre_id)
    • Response: JSON object of the created movie
    • Authentication: Requires a valid access token
    • Status Code: 201 Created
  • GET /movies/{id}
    • Description: Retrieve a specific movie by ID
    • Response: JSON object of the requested movie
    • Authentication: Requires a valid access token
    • Status Code: 200 OK or 404 Not Found
  • PUT /movies/{id}
    • Description: Update an existing movie
    • Request Body: JSON object with movie details to update
    • Response: JSON object of the updated movie
    • Authentication: Requires a valid access token
    • Status Code: 200 OK or 404 Not Found
  • DELETE /movies/{id}
    • Description: Delete a specific movie
    • Response: JSON object with deletion status
    • Authentication: Requires a valid access token
    • Status Code: 200 OK or 404 Not Found
  • POST /movies/{movie}/watch-later
    • Description: Add a movie to the user's watch later list
    • Response: JSON object with confirmation message
    • Authentication: Requires a valid access token
    • Status Code: 200 OK or 409 Conflict if already in list
  • DELETE /movies/{movie}/watch-later
    • Description: Remove a movie from the user's watch later list
    • Response: JSON object with confirmation message
    • Authentication: Requires a valid access token
    • Status Code: 200 OK or 404 Not Found if not in list
  • GET /watch-later
    • Description: Retrieve the user's watch later list
    • Response: JSON object with user details and list of movies
    • Authentication: Requires a valid access token
    • Status Code: 200 OK
These endpoints provide a comprehensive set of features for user authentication, movie management, and watch later functionality. All endpoints except for register and login require authentication using Laravel Sanctum, ensuring that only authorized users can access and modify the data.
Authentication

For user authentication, I implemented Laravel Sanctum. This provides a lightweight authentication system for SPAs, mobile applications, and simple token-based APIs.

    
php
Copy
protected $middlewareGroups = [ // ... 'api' => [ \Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class, 'throttle:api', \Illuminate\Routing\Middleware\SubstituteBindings::class, ], ];
Testing

One of the most crucial parts of this project was implementing comprehensive tests. Here's an example of a test for adding a movie:

    
php
Copy
// tests/Feature/MovieTest.php test('user can add a movie to database', function () { Sanctum::actingAs($this->user); $genre = Genre::first(); $movieData = [ 'title' => 'Test Movie', 'description' => 'This is a test movie description', 'release_date' => '2023-01-01', 'genre_id' => $genre->id, ]; $response = $this->postJson('/api/movies', $movieData); $response->assertStatus(201) ->assertJsonStructure([ 'id', 'title', 'description', 'release_date', 'genre_id', 'created_at', 'updated_at', ]); $this->assertDatabaseHas('movies', [ 'title' => 'Test Movie', 'description' => 'This is a test movie description', 'release_date' => '2023-01-01', 'genre_id' => $genre->id, ]); });

Another example of a test that checks if the user can remove a movie from the watch later list:

    
php
Copy
test('user can remove a movie from watch later list', function () { Sanctum::actingAs($this->user); $movie = $this->movies->first(); $this->user->watchLater()->attach($movie->id); $response = $this->deleteJson("/api/movies/{$movie->id}/watch-later"); $response->assertStatus(200) ->assertJson([ 'message' => "The movie \"{$movie->title}\" has been removed from your watch later list." ]); $this->assertDatabaseMissing('movie_user', [ 'user_id' => $this->user->id, 'movie_id' => $movie->id, ]); });
Testing Suite
Testing the API with Postman

To help you get started with testing our Movie Database API, I'll walk you through the process of using Postman to interact with some of our endpoints. Postman is a popular API client that makes it easy to send requests and analyze responses.

Base URL

For all these examples, we'll be using the following base URL:

    
text
Copy
https://libe.dev/demo/movie-api/v1/api

Make sure to prepend this base URL to all the endpoints we'll be testing.

  • User Registration
    Let's start by registering a new user:
    • Method: POST
    • Endpoint: /register
    • Headers:
      • Content-Type: application/json
    • Body (raw JSON):
          
      json
      Copy
      { "name": "John Doe", "email": "john@example.com", "password": "securepassword123" }
    • Upon successful registration, you should receive a response with the user's details and an access token.
  • User Login
    Now, let's log in with the newly created user:
    • Method: POST
    • Endpoint: /login
    • Headers:
      • Content-Type: application/json
    • Body (raw JSON):
          
      json
      Copy
      { "email": "john@example.com", "password": "securepassword123" }
      The response should include an access token. Copy this token as we'll need it for authenticated requests.
  • Creating a Movie
    Let's create a new movie entry:
    • Method: POST
    • Endpoint: /movies
    • Headers:
      • Content-Type: application/json
      • Authorization: Bearer {your_access_token}
    • Body (raw JSON):
          
      json
      Copy
      { "title": "Inception", "description": "A thief who enters the dreams of others to steal secrets from their subconscious.", "release_date": "2010-07-16", "genre_id": 1 }
      You should receive a response with the details of the newly created movie.
  • Adding a Movie to Watch Later
    Finally, let's add a movie to our Watch Later list:
    • Method: POST
    • Endpoint: /movies/{movie_id}/watch-later
    • Headers:
      • Authorization: Bearer {your_access_token}

    Replace {movie_id} with the ID of the movie you want to add to your Watch Later list.

    You should receive a confirmation that the movie has been added to your Watch Later list.
Challenges and Solutions
  • Data Validation - I implemented detailed validation rules in the request classes:
        
    php
    Copy
    public function rules() { return [ 'title' => 'required|string|max:255', 'description' => 'required|string', 'release_date' => 'required|date', 'genre_id' => 'required|exists:genres,id', ]; }
  • Authentication - Integrating Sanctum required careful configuration in the config/sanctum.php file.
  • Testing Edge Cases - Writing tests for various scenarios helped identify and fix potential issues early:
        
    php
    Copy
    // tests/Feature/MovieTest.php test('adding a movie with invalid data', function () { Sanctum::actingAs($this->user); $invalidData = [ 'title' => '', 'description' => '', 'release_date' => 'not-a-date', 'genre_id' => 'not-a-number', ]; $response = $this->postJson('/api/movies', $invalidData); $response->assertStatus(422) ->assertJsonValidationErrors(['title', 'description', 'release_date', 'genre_id']); });
Lessons Learned
  • The importance of TDD: Writing tests before implementing features helped me think through the API design more thoroughly.
  • Laravel's ecosystem: The project showcased how Laravel's built-in tools and packages can significantly speed up API development.
  • API design principles: I learned the importance of consistent response structures and proper HTTP status code usage.
Future Improvements

While the current implementation is solid, there's always room for improvement:

  • Implement pagination for listing movies
  • Add more advanced filtering and sorting options
  • Integrate with a third-party movie database API for additional information
  • Implement caching to improve performance
Conclusions

Developing this Movie Database API with Laravel highlighted the framework’s intuitive syntax and robust capabilities. This project showcases how to build a secure, well-tested API that can support a range of front-end applications.

I hope this guide gives you an insight into Laravel’s potential for API development. Happy coding!

Luis Velazquez Bilbao
Software Engineer