Skip to content

Routing

Routes register themselves from config/jsonapi.php — one route per discovered type × allowed operation, per server. This is the opposite of the Symfony bundle (where you import a route type by hand). This page covers the auto-registration, the manual-placement macro, route names, and route:cache safety (PLAN decision 4).

Auto-registration

On boot, the package registers every allowed operation for every discovered resource under its server's prefix, middleware, and optional domain from config/jsonapi.php:

GET    /api/albums                          jsonapi.albums.index
GET    /api/albums/{id}                      jsonapi.albums.show
POST   /api/albums                           jsonapi.albums.create
PATCH  /api/albums/{id}                      jsonapi.albums.update
DELETE /api/albums/{id}                      jsonapi.albums.delete
GET    /api/albums/{id}/{relationship}       …  # related + relationship read/mutation

Only the operations a resource allows are emitted (readOnly/operations — see resources); the rest simply don't exist.

Route names

Route names are stable and predictable:

  • default server: jsonapi.{type}.{action} — e.g. jsonapi.albums.show;
  • named server: jsonapi.{server}.{type}.{action} — e.g. jsonapi.admin.albums.show.

Use them with route() and URL::route() as normal. The docs routes follow the same scheme (jsonapi.openapi.json, jsonapi.admin.openapi.json) — see openapi.

Id route constraints

An Id field's matchAs() becomes a ->where('id', …) route constraint, so a malformed id is a clean route miss (404) rather than a 500 deep in the provider:

Id::make()->encodeUsing(new ProductIdCodec())->matchAs('prod-[0-9a-f]+');
// → route {id} constrained to prod-[0-9a-f]+

uuid() / ulid() id strategies apply their natural format constraint automatically.

Server middleware and domain

Attach middleware or a host constraint per server in the config:

'servers' => [
    'default' => ['prefix' => 'api',   'middleware' => ['throttle:api'], 'domain' => null],
    'admin'   => ['prefix' => 'admin', 'middleware' => ['auth:sanctum'], 'domain' => 'admin.example.test'],
],

Manual placement

Opt out of auto-registration and place the routes yourself with the Route::jsonApi() macro — useful to nest them inside your own route group (extra middleware, a versioned prefix):

// A service provider's register():
use haddowg\JsonApiLaravel\Facades\JsonApi;
JsonApi::ignoreRoutes();

// routes/api.php:
Route::middleware('auth:sanctum')->prefix('v2')->group(function () {
    Route::jsonApi();          // the default server's routes here
    Route::jsonApi('admin');   // a named server's routes here
});

Route::jsonApi(?string $server = null) emits the same operation-gated route set the auto-registration would, inside the surrounding group.

route:cache safety

Route registration reads the cached discovery snapshot rather than re-scanning the filesystem, so php artisan route:cache works. Warm the snapshot with the optimize pipeline (php artisan jsonapi:optimize) before caching routes in production; in development the scan runs live on first boot.