skip to Main Content

I’m learning Laravel on an existing 11.29.0 project.

I have two routes defined:

//api.php

Route::get('/items/approved', [ItemController::class, 'approved']);

Route::get('/items/{id}', [ItemController::class, 'show']);

In my controller I have this:

//ItemController.php

namespace AppHttpControllers;
use IlluminateHttpResourcesJsonAnonymousResourceCollection;
use AppHttpResourcesItemResource;

class ItemController extends Controller{

  //MARK: Approved
  public function approved(): AnonymousResourceCollection
  {
    error_log('inside approved');

    //...
  }

  //MARK: Show
  public function show($id): ItemResource|JsonResponse
  {
    error_log('inside show');
    
    //...
  }
}

When I do a GET request to /items/approved it always hits the show($id) function.

Shouldn’t the fact that /items/approved comes before /items/{id} make it so the approved() function is called? What am I missing?

3

Answers


  1. This happens because both the routes are same and Laravel could not make difference between

    '/items/approved'
    

    and

    '/items/{id}'
    

    since "approved" and "{id}" are same endpoints.

    If you enter same path twice (or more) the last one will replace all the above.

    Fix : Just change the route paths like

    'items-approved'
    

    and

    'items/{id}'
    

    You can see it works as expected!

    Login or Signup to reply.
  2. The {id} route parameter in /items/{id} is a wildcard that will match ANY string in that position. Even approved also falls under this.

    You can try these

    1. Wildcard
    2. Grouping
    3. naming routes.

    Route::get('/items/{id}', [ItemController::class, 'show'])->where('id', '[0-9]+');
    

    Route::prefix('items')->group(function () {
        Route::get('/approved', [ItemController::class, 'approved']);
        Route::get('/{id}', [ItemController::class, 'show']);
    });
    

    Route::get('/items/approved', [ItemController::class, 'approved'])->name('items.approved');
    
    Login or Signup to reply.
  3. The Reason is laravel is treating both as same, for example if you send a request
    http://127.0.0.1:8000/items/approved

    http://127.0.0.1:8000/items/1232

    Technically it should work as expected but due to laravel wildcard it look for but Laravel interprets {id} in /items/{id} as a wildcard parameter that can match any value, including the string approved. As a result, the /items/{id} route matches the request /items/approved before Laravel has a chance to match it against the /items/approved route.

    Route::get('/items/approved', [ItemController::class, 'approved']);
    Route::get('/items/{id}', [ItemController::class, 'show']);
    

    How to fix:
    Laravel routes are evaluated top-down, but more generic routes like {id} can override specific ones if their patterns are not restricted.
    The where() method allows you to fine-tune what a route parameter can match.
    Example

    Route::get('/items/approved', function(){
      return 'Item approved';  
    });
    Route::get('/items/{id}',function(){
        return 'Item id';
    })->where('id', '[0-9]+');
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search