Route::apiResource
Registers the five API CRUD routes, omitting the HTML create and edit forms.
apiResource(string $name, string $controller, array $options = []): Illuminate\Routing\PendingResourceRegistrationParameters
| Parameter | Type | Description |
|---|---|---|
$$name | string | The resource name used for the URI segment and the route-name prefix; dot notation nests resources. |
$$controller | string | The controller class name. php artisan make:controller PhotoController --api generates one without create and edit methods. |
$$optionsoptional | array= [] | Registration options such as only, except, names, parameters and middleware, given as an array rather than chained. |
Return value
Illuminate\Routing\PendingResourceRegistrationA PendingResourceRegistration supporting the same fluent modifiers as resource(): only(), except(), names(), parameters(), scoped(), shallow(), middleware(), middlewareFor(), withoutMiddlewareFor(), missing() and withTrashed().
Errors
Nothing at registration. At request time an unresolved implicit binding returns 404 unless missing() overrides it.
Route::apiResource is Route::resource with the two form-rendering routes removed. create and edit exist to serve HTML pages; a JSON API has no use for them, so it registers five routes:
| Verb | URI | Action | Route Name |
|---|---|---|---|
| GET | /photos | index | photos.index |
| POST | /photos | store | photos.store |
| GET | /photos/{photo} | show | photos.show |
| PUT/PATCH | /photos/{photo} | update | photos.update |
| DELETE | /photos/{photo} | destroy | photos.destroy |
When to reach for it
Any controller in routes/api.php that maps to a model. Register several at once with Route::apiResources(['photos' => PhotoController::class, 'posts' => PostController::class]).
What trips people up
- Route names are not namespaced for you.
apiResource('photos', ...)inroutes/api.phpstill names its routesphotos.index, which collides with the web resource of the same name. Wrap it inRoute::name('api.')->group(...). - The /api prefix comes from the file, not from this method — the same call in
web.phpproduces unprefixed URIs. - update answers both PUT and PATCH, so a client sending PATCH lands in the same method and must be handled there.
- Generate the matching controller with
--apiso it has no deadcreate/editstubs.
Example
<?php
// routes/api.php — the /api prefix is applied by the framework.
use App\Http\Controllers\Api\PhotoController;
use Illuminate\Support\Facades\Route;
Route::name('api.')->middleware('auth:sanctum')->group(function () {
Route::apiResource('photos', PhotoController::class);
});
echo route('api.photos.index');
echo PHP_EOL;
echo route('api.photos.show', ['photo' => 3]);
// http://example.com/api/photos
// http://example.com/api/photos/3When to use it
- Expose a model as a JSON API behind auth:sanctum in routes/api.php.
- Register several API resources at once with Route::apiResources().
- Drop destroy from a public API with ->except(['destroy']).
- Nest comments under photos and scope the child binding by slug.
Frequently asked questions
What is the difference between apiResource and resource?
apiResource registers five routes instead of seven — it omits create and edit, which exist only to render HTML forms. Everything else, including the PUT/PATCH update route, is identical.
Does apiResource add the /api prefix?
No. That prefix comes from routes/api.php being loaded with an apiPrefix in bootstrap/app.php. The same apiResource call in web.php registers unprefixed URIs.
How do I stop my API route names clashing with my web route names?
Wrap the API routes in Route::name('api.')->group(...) so they become api.photos.index and so on. apiResource does not namespace names on its own.
More examples
Several resources, and trimming the set
apiResources registers two full five-route sets in one statement. middlewareFor is the finer tool: it attaches auth:sanctum to the three writing actions while index and show stay reachable without a token, which is otherwise two separate apiResource calls with only() and except().
<?php
use App\Http\Controllers\PhotoController;
use App\Http\Controllers\PostController;
use Illuminate\Support\Facades\Route;
Route::apiResources([
'photos' => PhotoController::class,
'posts' => PostController::class,
]);
// A read-only endpoint: index and show only.
Route::apiResource('tags', TagController::class)->only(['index', 'show']);
// Require auth for writes but leave reads public.
Route::apiResource('comments', CommentController::class)
->middlewareFor(['store', 'update', 'destroy'], 'auth:sanctum');
echo route('tags.index');
echo PHP_EOL;
echo route('comments.store');
// http://example.com/api/tags
// http://example.com/api/commentsNested API resource with scoped binding
scoped() does two things at once: it resolves the comment by its slug column instead of its id, and it constrains the lookup to the parent photo's comments relationship. A slug belonging to a different photo returns 404 rather than leaking the wrong record.
<?php
use App\Http\Controllers\Api\PhotoCommentController;
use Illuminate\Support\Facades\Route;
Route::apiResource('photos.comments', PhotoCommentController::class)
->scoped(['comment' => 'slug']);
// Produces URIs such as:
// GET /api/photos/{photo}/comments
// POST /api/photos/{photo}/comments
// GET /api/photos/{photo}/comments/{comment:slug}
// PUT /api/photos/{photo}/comments/{comment:slug}
// DELETE /api/photos/{photo}/comments/{comment:slug}
echo route('photos.comments.show', ['photo' => 3, 'comment' => 'nice-shot']);
// http://example.com/api/photos/3/comments/nice-shot
Discussion