In Laravel, what does Route::apiResource change compared with Route::resource, and how do only() and except() narrow a resource?
answer
- no HTML form pages
- drops create and edit
- make:controller --api
- only() intersects, except() subtracts
- fluent only() replaces the api list
basics
~20 sRoute::apiResource registers the resource routes minus the create and edit form pages, leaving index, store, show, update and destroy. only([...]) keeps just the listed actions and except([...]) removes listed ones; make:controller --api stubs the matching five methods.
solid answer
~30 s`Route::apiResource('books', BookController::class)` is `Route::resource` with `only` preset to `index`, `store`, `show`, `update` and `destroy`, because a JSON client has no use for the `create` and `edit` HTML form pages. `apiResources([...])` registers several at once, and `make:controller BookController --api` stubs exactly those five methods. On any resource, `->only(['index', 'show'])` keeps only the listed actions and `->except(['destroy'])` removes the listed ones; `except` is applied after `only`. One trap: calling the fluent `->only()` on an `apiResource` **replaces** its preset list, so `->only(['index', 'create'])` does register `create`; use `->except()` to narrow an API resource.
code
php · 14 lines<?php
use App\Http\Controllers\Api\AuthorController;
use App\Http\Controllers\Api\BookController;
use App\Http\Controllers\Api\LoanController;
use Illuminate\Support\Facades\Route;
Route::apiResources([
'books' => BookController::class,
'authors' => AuthorController::class,
]);
Route::apiResource('loans', LoanController::class)
->except(['destroy']);go deeper
Know that apiResource drops create and edit, and that only() and except() choose which resource routes exist.
Explain the intersection-then-difference order, the deferred PendingResourceRegistration, and the fluent only() override on apiResource.
Keep the exposed surface minimal by trimming unused actions and verifying it in route:list or a route test.
Standardise how web and API resources are split across route files and controllers so the public API surface is reviewable.
## Why an API resource exists A web **resource** in Laravel has seven actions, two of which, `create` and `edit`, only return HTML forms. A JSON API has no forms: clients send the data straight to `store` and `update`. `Route::apiResource()` registers the resource without those two pages. ```php Route::apiResource('books', BookController::class); ``` | Verb | URI | Action | Name | |---|---|---|---| | GET | `/books` | `index` | `books.index` | | POST | `/books` | `store` | `books.store` | | GET | `/books/{book}` | `show` | `books.show` | | PUT/PATCH | `/books/{book}` | `update` | `books.update` | | DELETE | `/books/{book}` | `destroy` | `books.destroy` | Related helpers: - `Route::apiResources(['books' => BookController::class, 'authors' => AuthorController::class])` registers several. - `php artisan make:controller BookController --api` generates the five methods without `create` and `edit`; `--model=Book --api` type-hints the model in them. - In a Laravel 13 app the `routes/api.php` file itself is opt-in, created by `php artisan install:api`; `apiResource` works in any routes file. ## How `only()` and `except()` work Both are methods on the pending registration that `resource()` returns, and both can also be passed as options. 1. The registrar starts from the default action list. 2. If `only` is set, it keeps the **intersection** with that list. 3. If `except` is set, it **removes** those actions from what is left. ```php Route::resource('books', BookController::class)->only(['index', 'show']); Route::resource('loans', LoanController::class)->except(['edit', 'update']); ``` Unknown action names are simply ignored, and the relative order of the registered routes stays the conventional one whatever order you list them in. ## The `apiResource` + `only()` trap `apiResource()` is implemented as `resource()` with an `only` option preset to the five API actions. The fluent `->only()` call **overwrites** that option rather than intersecting with it: ```php // registers index AND create: the preset list was replaced Route::apiResource('books', BookController::class)->only(['index', 'create']); ``` `->except()` behaves as expected on an API resource, because it subtracts from the preset list. So to narrow an API resource, prefer `except()`, or list only actions that belong to the API set. ## Registration is deferred `Route::resource()` returns a `PendingResourceRegistration`; the routes are actually added when that object is destroyed (or when `register()` is called). That is why the fluent calls, `only()`, `except()`, `names()`, `middleware()` and friends, all take effect even though they come after `resource()` in the chain. ## Choosing between them | Situation | Use | |---|---| | Blade screens with separate form pages | `Route::resource` | | JSON API consumed by a SPA or mobile app | `Route::apiResource` | | Read-only catalogue browsing | `->only(['index', 'show'])` | | Records that must never be deleted | `->except(['destroy'])` | Trimming unused routes matters: every registered route is an endpoint someone can call, and a stub method left behind by the generator may do something unexpected, or nothing, when requested. ## Common mistakes - **Using `resource` for a JSON API** and leaving `create` and `edit` to return HTML views, or nothing, to API clients. - **Narrowing an API resource with `->only()`** and accidentally re-enabling a form route, as described above. - **Trimming routes but not methods**: removing `destroy` from the routes while leaving a half-written `destroy()` method invites someone to route it later without review. - **Assuming `only()` hides a route from `route:list`**: excluded actions are never registered, so they neither appear nor respond, which is exactly what makes `route:list` a reliable audit. - **Passing action names with typos** such as `'delete'` in `except()`: unknown names are ignored silently, so the real `destroy` route stays registered.
- Why does Route::apiResource('books', X)->only(['index', 'create']) register a create route?`apiResource()` presets the `only` option to the five API actions and returns the same pending registration a normal resource does. The fluent `only()` overwrites that option, so the full seven-action list is intersected with `index` and `create`. Use `except()` to narrow an API resource instead.
- When are the resource's routes actually added to the router?When the `PendingResourceRegistration` returned by `resource()` is destroyed, typically at the end of the statement, or earlier if `register()` is called. Deferring registration is what lets fluent calls like `only()` or `names()` shape the routes after `resource()` has returned.
saying these in an interview costs you the question
- apiResource also removes the show route because APIs list everything.
- only() and except() hide routes from route:list but still register them.
- apiResource(...)->only([...]) can never add create or edit.
- An API resource must live in routes/api.php to work.
- make:controller --api generates the seven methods with JSON return types.