DEV
Home About Services Projects Blog Contact
Hire me
Laravel & PHP

Building a Robust REST API with Laravel 11: The Complete Guide

🇫🇷 Lire en français
Building a Robust REST API with Laravel 11: The Complete Guide

Three years ago, I joined a startup that used Laravel for its backend. My first ticket: "add an endpoint to fetch a user's orders." Simple, right? Two days later, the API was in production... and completely broken. N+1 queries everywhere, no authentication on routes, inconsistent JSON responses depending on the endpoint. A silent disaster.

Since then, I've built dozens of APIs with Laravel. I've learned from my mistakes, often painfully. Here's the guide I wish I'd had back then.

Why Laravel is Perfect for REST APIs

Laravel isn't just a web framework. It's a complete platform that natively includes everything you need for a professional, maintainable API:

  • Sanctum — token-based authentication without complex configuration
  • API Resources — transform your Eloquent models into clean JSON
  • Form Requests — centralized validation outside of controllers
  • Rate Limiting — built-in protection in route definitions
  • Queues & Events — async processing to avoid blocking responses

Project Structure: Think Scalability From Day One

The first thing to do before writing a line of code: version your API. Even if you only have one version today, you'll be grateful in six months when you need to introduce breaking changes without breaking existing clients.

app/
├── Http/
│   ├── Controllers/Api/V1/
│   │   ├── AuthController.php
│   │   ├── UserController.php
│   │   └── PostController.php
│   ├── Requests/Api/V1/
│   │   ├── LoginRequest.php
│   │   └── StorePostRequest.php
│   └── Resources/Api/V1/
│       ├── UserResource.php
│       └── PostResource.php

Authentication with Laravel Sanctum

Sanctum is the official solution for securing Laravel APIs. No manual JWT configuration, no complicated third-party libraries. Install it in three commands:

composer require laravel/sanctum
php artisan vendor:publish --provider="Laravel\Sanctum\SanctumServiceProvider"
php artisan migrate

Here's a complete authentication controller with error handling:

class AuthController extends Controller
{
    public function login(LoginRequest $request): JsonResponse
    {
        $user = User::where('email', $request->email)->first();

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

        $token = $user->createToken('api-token', ['*'], now()->addDays(30))->plainTextToken;

        return response()->json([
            'token'   => $token,
            'expires' => now()->addDays(30)->toIso8601String(),
            'user'    => new UserResource($user),
        ]);
    }

    public function logout(): JsonResponse
    {
        auth()->user()->currentAccessToken()->delete();
        return response()->json(['message' => 'Successfully logged out.']);
    }
}

API Resources: Only Expose What's Needed

This is probably the most underused feature by junior developers — and the most important. An API Resource controls exactly what you send to the client, preventing accidentally exposing sensitive fields:

class UserResource extends JsonResource
{
    public function toArray(Request $request): array
    {
        return [
            'id'         => $this->id,
            'name'       => $this->name,
            'email'      => $this->email,
            'role'       => $this->role,
            'avatar_url' => $this->avatar_url,
            'created_at' => $this->created_at->toIso8601String(),
            // Never: 'password', 'remember_token'...
        ];
    }
}

Validation with Form Requests: Get It Out of Controllers

Absolute rule: never validate directly inside controllers. Your controller should do one thing — orchestrate the response. Validation belongs in a dedicated Form Request:

class StorePostRequest extends FormRequest
{
    public function rules(): array
    {
        return [
            'title'       => ['required', 'string', 'max:255'],
            'content'     => ['required', 'string', 'min:100'],
            'category_id' => ['required', 'exists:categories,id'],
            'tags'        => ['nullable', 'array'],
            'tags.*'      => ['exists:tags,id'],
        ];
    }
}

Error Handling: Consistency Above All

A good API returns structured, predictable errors. Your clients — whether a mobile app or React frontend — need to handle errors uniformly. In bootstrap/app.php (Laravel 11):

->withExceptions(function (Exceptions $exceptions) {
    $exceptions->render(function (ModelNotFoundException $e, Request $request) {
        if ($request->is('api/*')) {
            return response()->json(['message' => 'Resource not found.', 'error' => 'not_found'], 404);
        }
    });
})

Eliminating N+1 Queries with Eager Loading

One of the most frequent mistakes in Laravel APIs: fetching collections without pre-loading relationships. The result: 1 query for the list + 1 query per item to load the relation = N+1 queries.

// ❌ N+1 queries: production disaster
$posts = Post::all();
foreach ($posts as $post) {
    echo $post->author->name; // 1 query PER post
}

// ✅ Eager loading: always just 2 queries
$posts = Post::with(['author', 'category', 'tags'])->paginate(20);

Conclusion: The 3 Golden Rules of a Good API

  1. Version from day one — trivial to add at the start, painful to retrofit later.
  2. Consistent responses — same JSON structure for all successes, same structure for all errors.
  3. Auto-document — use Scramble or L5-Swagger. An undocumented API is an unusable API.

The API you build today, others will use tomorrow. Make their life easy — including your future self.

Related articles

Écrire sur WhatsApp