Mark Lowel Montealto — Full Stack Developer & DevOps Engineer

Mark Lowel
Montealto

Full Stack Developer & DevOps Engineer

Blog

July 2024

·

2 min read

Build a Production REST API with Laravel: Auth, Validation & Resources

Build a production-grade Laravel REST API with Sanctum authentication, Form Request validation, API Resources, and consistent error responses.

Introduction

  • Laravel is a go-to backend for full-stack projects pairing with React/Next.js or Angular frontends.
  • This guide covers the core pillars of a production API: authentication (Sanctum), validation (Form Requests), transformation (API Resources), and error handling.
  • Assumption: fresh Laravel 11 app, no existing API setup.

Project Setup

  • Install Laravel: composer create-project laravel/laravel my-api.
  • Configure .env: DB_*, APP_URL, SANCTUM_STATEFUL_DOMAINS.
  • Install Sanctum: composer require laravel/sanctum && php artisan vendor:publish --tag=sanctum-config && php artisan migrate.

Authentication with Laravel Sanctum

  • Sanctum provides token-based API authentication (SPA cookies for same-domain, bearer tokens for mobile/third-party).
  • Two flows: stateful (SPA with CSRF cookie) and stateless (token per API client).
// routes/api.php
Route::post('/auth/login', [AuthController::class, 'login']);
Route::post('/auth/register', [AuthController::class, 'register']);
 
Route::middleware('auth:sanctum')->group(function () {
    Route::get('/user', fn(Request $r) => $r->user());
    Route::post('/auth/logout', [AuthController::class, 'logout']);
    Route::apiResource('posts', PostController::class);
});
// AuthController.php (login method)
public function login(LoginRequest $request): JsonResponse
{
    if (! Auth::attempt($request->only('email', 'password'))) {
        return response()->json(['message' => 'Invalid credentials'], 401);
    }
    $token = Auth::user()->createToken('api-token')->plainTextToken;
    return response()->json(['token' => $token]);
}

Form Request Validation

  • Form Requests centralise validation rules and authorisation logic away from controllers.
  • php artisan make:request StorePostRequest.
// app/Http/Requests/StorePostRequest.php
class StorePostRequest extends FormRequest
{
    public function authorize(): bool { return true; }
 
    public function rules(): array
    {
        return [
            'title'    => ['required', 'string', 'max:255'],
            'body'     => ['required', 'string'],
            'category' => ['required', 'in:Frontend,Backend,DevOps,Cloud'],
            'tags'     => ['nullable', 'array'],
            'tags.*'   => ['string', 'max:32'],
        ];
    }
}
  • Validation failures auto-return 422 with errors JSON — no extra handling needed.

API Resources: Transforming Models

  • API Resources decouple your database schema from your API contract.
  • php artisan make:resource PostResource and PostCollection.
// app/Http/Resources/PostResource.php
class PostResource extends JsonResource
{
    public function toArray(Request $request): array
    {
        return [
            'id'         => $this->id,
            'title'      => $this->title,
            'slug'       => $this->slug,
            'excerpt'    => $this->excerpt,
            'category'   => $this->category,
            'tags'       => $this->tags,
            'created_at' => $this->created_at->toIso8601String(),
        ];
    }
}

Controller Pattern

  • Keep controllers thin: validate → authorise → act → transform → respond.
// PostController.php
public function store(StorePostRequest $request): JsonResponse
{
    $post = Post::create($request->validated() + ['user_id' => $request->user()->id]);
    return (new PostResource($post))->response()->setStatusCode(201);
}
 
public function index(): AnonymousResourceCollection
{
    return PostResource::collection(Post::paginate(15));
}

Consistent Error Responses

  • Override render in app/Exceptions/Handler.php to always return JSON for API routes.
  • Standard error envelope: { "message": "...", "errors": { ... } }.
  • HTTP status codes: 200 (OK), 201 (Created), 204 (No Content), 400 (Bad Request), 401 (Unauthenticated), 403 (Forbidden), 404 (Not Found), 422 (Validation), 500 (Server Error).

Pagination

  • Post::paginate(15) wrapped in PostResource::collection() auto-generates Laravel's pagination envelope with data, links, meta.
  • Use simplePaginate for cursor-style pagination on large datasets.

Rate Limiting

  • Laravel's built-in rate limiter: define in AppServiceProvider with RateLimiter::for('api', ...).
  • throttle:60,1 middleware limits to 60 requests per minute per IP.

API Versioning

  • Prefix routes: Route::prefix('v1')->group(...).
  • Separate controllers per version or use transformation-layer versioning in Resources.

Testing the API

  • php artisan make:test PostApiTest — use $this->postJson('/api/v1/posts', [...]) for HTTP assertions.
  • RefreshDatabase trait for a clean DB per test.
  • Test authentication: $this->actingAs($user, 'sanctum').

Conclusion

  • Form Requests + API Resources enforce a clean separation between input, business logic, and output.
  • Sanctum covers both SPA and mobile clients without needing a full OAuth server.
  • Next steps: add role-based policies (php artisan make:policy), queue email notifications, and Dockerize for deployment.