Laravel Route Order: Custom Routes Before Resource Routes

πŸ“– 2 minutes read

When mixing custom routes with apiResource, order matters. Specific routes must come before parameterized ones.

The Problem

// ❌ WRONG: Custom route comes AFTER resource
Route::apiResource('notifications', NotificationController::class);
Route::get('/notifications/stats', [NotificationController::class, 'stats']);

Result:

GET /api/notifications/stats
# Laravel matches this to show($id='stats') ❌
# Returns 404: Notification not found

The Solution

// βœ… CORRECT: Custom routes BEFORE resource
Route::get('/notifications/stats', [NotificationController::class, 'stats'])
    ->name('notifications.stats');

Route::post('/notifications/actions/mark-all-read', [NotificationController::class, 'markAllAsRead'])
    ->name('notifications.mark-all-read');

Route::apiResource('notifications', NotificationController::class)
    ->only(['index', 'show', 'update', 'destroy']);

Route Matching Order

Laravel matches routes top to bottom:

  1. GET /notifications/stats β†’ matches first route βœ“
  2. POST /notifications/actions/mark-all-read β†’ matches second route βœ“
  3. GET /notifications/abc123 β†’ matches apiResource show route βœ“

Naming Convention for Custom Routes

Use descriptive paths for clarity:

// Collection-level actions
Route::get('/users/export', ...);        // GET /api/users/export
Route::post('/users/import', ...);       // POST /api/users/import

// Named action routes
Route::post('/orders/actions/bulk-cancel', ...);
Route::get('/products/stats', ...);

// Avoid generic prefixes that might conflict
Route::get('/notifications/meta', ...);  // βœ“ Good: 'meta' won't be a UUID
Route::get('/notifications/all', ...);   // ⚠️ Could conflict if 'all' is valid ID

Debug Routes

Use php artisan route:list to verify order:

php artisan route:list --path=notifications

# Output shows registration order:
GET     api/notifications/stats          notifications.stats
POST    api/notifications/actions/...    notifications.mark-all-read  
GET     api/notifications                notifications.index
GET     api/notifications/{notification} notifications.show

Remember: Specific beats generic. Always define custom routes before resource routes.

Daryle De Silva

VP of Technology

11+ years building and scaling web applications. Writing about what I learn in the trenches.

Comments

Leave a Reply

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