Table of Contents
📖 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:
GET /notifications/stats→ matches first route ✓POST /notifications/actions/mark-all-read→ matches second route ✓GET /notifications/abc123→ matchesapiResourceshow 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.
Leave a Reply