Variable in apiResource 404 not found

Viewed 47

I'm learning Laravel and the apiResource. My page is returning 404. I need my second path to be a variable because it is to show the sub-categories from the products, so it needs to drill down depending on what ID is passed from the productId.

/products - shows the parent products (ie.transport)
/products/{id} - shows (ie.cars)
/products/{id}/{car-types} - shows (ie.sports-cars, luxury-cars, 4wd)
/products/{id}/{car-types}/{id} - shows (ie. the actual car)

Routes

Route::apiResource('/products', ProductController::class);
Route::apiResource('/products.{product-types}', ProductTypeController::class);
2 Answers

You can do something like this

Routes

Route::apiResource('products', ProductController::class)
    ->only(['index', 'show'])
    ->parameters(['products' => 'id']);

Route::apiResource('products.product-types', ProductTypeController::class)
    ->only(['index', 'show'])
    ->parameters([
        'products' => 'pid',
        'product-types' => 'id',
    ]);

URL

/products
/products/{id}
/products/{pid}/product-types
/products/{pid}/product-types/{id}

The error here is caused by your use of curly braces ({, }) in your apiResource name parameter, these are not required (check the #nested resource documentation for more info).

So to fix your issue, all you need to do is remove the curly braces.

Route::apiResource('/products', ProductController::class);
Route::apiResource('/products.product-types', ProductTypeController::class);

This will result in standardised routes being produced (use php artisan route:list in your terminal to get an output of your available configured routes by the way).

GET|HEAD    products ................................................................... products.index › ProductController@index
POST        products ................................................................... products.store › ProductController@store
GET|HEAD    products/{product} ........................................................... products.show › ProductController@show
PUT|PATCH   products/{product} ....................................................... products.update › ProductController@update
DELETE      products/{product} ..................................................... products.destroy › ProductController@destroy
GET|HEAD    products/{product}/product-types ......................... products.product-types.index › ProductTypeController@index
POST        products/{product}/product-types ......................... products.product-types.store › ProductTypeController@store
GET|HEAD    products/{product}/product-types/{product_type} ............ products.product-types.show › ProductTypeController@show
PUT|PATCH   products/{product}/product-types/{product_type} ........ products.product-types.update › ProductTypeController@update
DELETE      products/{product}/product-types/{product_type} ...... products.product-types.destroy › ProductTypeController@destroy

Update

If your desired route structure is /products/{category}/{sub_category}/{id} then the solution would be to not use the apiResource helper (as this generates standard/conventional REST URI structures) and instead define the route manually.

Route::group(['prefix' => '/products/{category?}/{sub_category?}/{id?}'], function () {
    Route::get('/', [DemoController::class, 'index']);
    // define other HTTP verb (POST, PATCH, DELETE etc.) routes
});

The above makes each of the parameters optional by using the ? operator.

Then for your index (or whatever you want to name it) method:

public function index(Request $request, $category = null, $sub_category = null, $id = null)
{
    // do something
}

You would need to perform sanity checks on the incoming parameters in order to return the correct data.

Alternatively, you could split the routes into groups and use individual controllers.

Route::group(['prefix' => '/categories'], function() {
    Route::get('/', [CategoryController::class, 'index']);
    
    Route::group(['prefix' => '/{type}'], function () {
        Route::get('/', [ProductTypeController::class, 'show']);

        Route::group(['prefix' => '/{sub_type}'], function () {
            Route::get('/', [ProductTypeController::class, 'show']);

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

The above results in the following route definitions:

  GET|HEAD   categories ......................... CategoryController@index
  GET|HEAD   categories/{type} ................ ProductTypeController@show
  GET|HEAD   categories/{type}/{sub_type} ..... ProductTypeController@show
  GET|HEAD   categories/{type}/{sub_type}/{id} .... ProductController@show

You should then be able to do things like:

/categories
/categories/transport
/categories/transport/cars
/categories/transport/cars/{ID} // ID could be an int or string, up to you
Related