Web Development

Laravel REST API Best Practices: Authentication, Validation, and Performance

December 08, 2024 β€’ 5 min read β€’ By Amey Lokare

Laravel REST API Best Practices: Authentication, Validation, and Performance

Building REST APIs in Laravel is straightforward, but making them production-ready requires attention to authentication, validation, error handling, and performance. Here are the patterns and practices I use in real-world projects.

πŸ” Authentication Strategies

1. Sanctum for SPA + Mobile Apps

Laravel Sanctum is perfect for:

  • Single-page applications (same domain)
  • Mobile applications
  • Token-based authentication
```php // config/sanctum.php 'stateful' => explode(',', env('SANCTUM_STATEFUL_DOMAINS', 'localhost,127.0.0.1')),

// Login endpoint public function login(Request $request) { $credentials = $request->validate([ 'email' => 'required|email', 'password' => 'required', ]);

if (Auth::attempt($credentials)) { $request->session()->regenerate(); return response()->json(['user' => Auth::user()]); }

return response()->json(['message' => 'Invalid credentials'], 401); } ```

2. API Tokens for Third-Party Access

For external integrations, use token-based auth:

```php // Generate token $token = $user->createToken('api-access')->plainTextToken;

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

3. OAuth2 with Passport (Advanced)

For public APIs with multiple clients, Laravel Passport provides OAuth2:

```bash php artisan passport:install ```

βœ… Request Validation

Use Form Requests

Never validate directly in controllers. Use Form Request classes:

```php // app/Http/Requests/StorePostRequest.php class StorePostRequest extends FormRequest { public function rules(): array { return [ 'title' => 'required|string|max:255', 'body' => 'required|string|min:100', 'category_id' => 'required|exists:categories,id', 'tags' => 'sometimes|array', 'tags.*' => 'exists:tags,id', ]; }

public function messages(): array { return [ 'title.required' => 'A title is required for the post.', 'body.min' => 'Post body must be at least 100 characters.', ]; } }

// In controller public function store(StorePostRequest $request) { // Validation already passed! $post = Post::create($request->validated()); return response()->json($post, 201); } ```

Custom Validation Rules

For complex business logic:

```php // app/Rules/ValidPhoneNumber.php class ValidPhoneNumber implements Rule { public function passes($attribute, $value) { return preg_match('/^\+?[1-9]\d{1,14}$/', $value); }

public function message() { return 'The :attribute must be a valid international phone number.'; } } ```

πŸ“¦ API Resource Classes

Transform models consistently using API Resources:

```php // app/Http/Resources/PostResource.php class PostResource extends JsonResource { public function toArray($request): array { return [ 'id' => $this->id, 'title' => $this->title, 'slug' => $this->slug, 'excerpt' => $this->excerpt, 'author' => new UserResource($this->whenLoaded('author')), 'category' => new CategoryResource($this->whenLoaded('category')), 'tags' => TagResource::collection($this->whenLoaded('tags')), 'published_at' => $this->published_at?->toIso8601String(), 'created_at' => $this->created_at->toIso8601String(), ]; } }

// In controller return new PostResource($post); // or for collections return PostResource::collection($posts); ```

🚨 Error Handling

Consistent Error Responses

Create a trait for standardized error responses:

```php // app/Traits/ApiResponder.php trait ApiResponder { protected function success($data, $message = null, $code = 200) { return response()->json([ 'success' => true, 'message' => $message, 'data' => $data, ], $code); }

protected function error($message, $code = 400, $errors = null) { return response()->json([ 'success' => false, 'message' => $message, 'errors' => $errors, ], $code); } } ```

Global Exception Handler

Customize `app/Exceptions/Handler.php`:

```php public function render($request, Throwable $exception) { if ($request->expectsJson()) { if ($exception instanceof ValidationException) { return $this->error( 'Validation failed', 422, $exception->errors() ); }

if ($exception instanceof ModelNotFoundException) { return $this->error('Resource not found', 404); } }

return parent::render($request, $exception); } ```

⚑ Performance Optimization

1. Eager Loading

Always eager load relationships to avoid N+1 queries:

```php // Bad: N+1 problem $posts = Post::all(); foreach ($posts as $post) { echo $post->author->name; // Query for each post! }

// Good: Eager loading $posts = Post::with(['author', 'category', 'tags'])->get(); ```

2. Pagination

Always paginate large datasets:

```php // Simple pagination $posts = Post::paginate(15);

// Cursor pagination (better for APIs) $posts = Post::cursorPaginate(15);

// Custom pagination return PostResource::collection($posts)->response(); ```

3. Caching

Cache expensive queries:

```php $posts = Cache::remember('featured_posts', 3600, function () { return Post::featured()->with('category')->get(); }); ```

4. Database Indexing

Add indexes for frequently queried columns:

```php // Migration $table->index('status'); $table->index('published_at'); $table->index(['status', 'published_at']); // Composite index ```

5. API Response Caching

Use HTTP caching headers:

```php return response()->json($data) ->header('Cache-Control', 'public, max-age=3600') ->header('ETag', md5(json_encode($data))); ```

πŸ”’ Security Best Practices

1. Rate Limiting

Protect your API from abuse:

```php // routes/api.php Route::middleware(['throttle:60,1'])->group(function () { Route::get('/posts', [PostController::class, 'index']); });

// Custom rate limits Route::middleware(['throttle:10,1'])->group(function () { Route::post('/login', [AuthController::class, 'login']); }); ```

2. CORS Configuration

Configure CORS properly in `config/cors.php`:

```php 'allowed_origins' => env('CORS_ALLOWED_ORIGINS', 'https://yourdomain.com'), 'allowed_methods' => ['GET', 'POST', 'PUT', 'PATCH', 'DELETE'], 'supports_credentials' => true, ```

3. Input Sanitization

Always sanitize user input:

```php // Use validation rules 'email' => 'required|email|max:255', 'content' => 'required|string|max:5000',

// Sanitize HTML if needed $clean = strip_tags($request->input('content')); ```

πŸ“Š API Versioning

Version your APIs for backward compatibility:

```php // routes/api.php Route::prefix('v1')->group(function () { Route::apiResource('posts', PostController::class); });

Route::prefix('v2')->group(function () { Route::apiResource('posts', V2\PostController::class); }); ```

πŸ§ͺ Testing APIs

Use Laravel's HTTP testing:

```php // tests/Feature/PostApiTest.php public function test_can_create_post() { $user = User::factory()->create();

$response = $this->actingAs($user, 'sanctum') ->postJson('/api/v1/posts', [ 'title' => 'Test Post', 'body' => 'Test body content...', 'category_id' => 1, ]);

$response->assertStatus(201) ->assertJsonStructure([ 'data' => ['id', 'title', 'slug'] ]); } ```

🎯 Key Takeaways

  • Use Form Requests for validation
  • API Resources for consistent responses
  • Eager load relationships
  • Paginate large datasets
  • Cache expensive operations
  • Rate limit to prevent abuse
  • Version your APIs
  • Test everything

Conclusion

Building production-ready Laravel APIs requires attention to detail across authentication, validation, error handling, and performance. These practices have helped me build APIs that are secure, fast, and maintainable.

Comments

Leave a Comment

Related Posts