skip to content

In Laravel, when would you register Route::singleton instead of Route::resource, and what do creatable() and apiSingleton change?

level: seniorimportance: should knowfreq 30%

answer

  1. one instance, no identifier
  2. show, edit, update only
  3. creatable(): create, store, destroy
  4. destroyable(): just destroy
  5. apiSingleton drops create and edit

basics

~20 s

Route::singleton is for a resource that exists at most once per parent or user, such as a book's cover: it registers show, edit and update with no id. creatable() adds create, store and destroy; apiSingleton drops the create and edit forms.

solid answer

~30 s

A **singleton resource** has at most one instance, so its URIs carry no identifier: `Route::singleton('books.cover', BookCoverController::class)` registers only `GET /books/{book}/cover` (`show`), `GET /books/{book}/cover/edit` (`edit`) and `PUT|PATCH /books/{book}/cover` (`update`), named `books.cover.show` and so on. It suits things like a member's profile or reading preferences (identified by the logged-in user) or a book's cover. `->creatable()` adds `create`, `store` and `destroy`; `->destroyable()` adds only `destroy`. `Route::apiSingleton` leaves out the form pages, so it registers `show` and `update`, plus `store` and `destroy` when creatable. `make:controller --singleton` stubs `create`, `store` and `destroy` as `abort(404)` unless you also pass `--creatable`.

code

php · 11 lines
php
<?php

use App\Http\Controllers\BookCoverController;
use App\Http\Controllers\ReadingPreferencesController;
use Illuminate\Support\Facades\Route;

Route::singleton('books.cover', BookCoverController::class)
    ->creatable();

Route::singleton('preferences', ReadingPreferencesController::class)
    ->middleware('auth');

go deeper

for a junior

Know that singleton resources model one-of-a-kind records and have no id in the URL.

for a middle

List the default singleton routes and what creatable(), destroyable() and apiSingleton add or remove.

for a senior

Decide when a record is truly a singleton, identify it safely from the authenticated user, and keep creation routes off when not needed.

for a principal

Weigh singleton URLs against future cardinality changes so public URLs stay stable as the domain evolves.

## What makes a resource a singleton A normal **resource** is a collection: many books, each addressed by an id. A **singleton resource** is something that exists **at most once** in its context, so there is nothing to list and no id to put in the URL: - the logged-in member's **profile** or **reading preferences**; - a book's **cover** image; - a library branch's **opening hours**. Using `Route::resource` for these produces nonsense routes (`index` of a single thing, `/cover/{cover}`), which is why Laravel has `Route::singleton`. ## Routes it registers ```php Route::singleton('books.cover', BookCoverController::class); ``` | Verb | URI | Action | Name | |---|---|---|---| | GET | `/books/{book}/cover` | `show` | `books.cover.show` | | GET | `/books/{book}/cover/edit` | `edit` | `books.cover.edit` | | PUT/PATCH | `/books/{book}/cover` | `update` | `books.cover.update` | A top-level singleton such as `Route::singleton('preferences', PreferencesController::class)` gives `/preferences` and `/preferences/edit`; the controller finds the record from the authenticated user rather than from the URL. ## Adding creation and deletion By default a singleton is assumed to always exist, so there is no way to create or delete it. Two methods change that: - **`->creatable()`** adds `create` (`GET .../cover/create`), `store` (`POST .../cover`) and `destroy` (`DELETE .../cover`). - **`->destroyable()`** adds only `destroy`, for something that can be removed but is created elsewhere. ## API singletons `Route::apiSingleton()` is to `singleton` what `apiResource` is to `resource`: it leaves out the HTML form pages. | Registration | Actions | |---|---| | `Route::singleton(...)` | `show`, `edit`, `update` | | `Route::singleton(...)->creatable()` | `create`, `store`, `show`, `edit`, `update`, `destroy` | | `Route::singleton(...)->destroyable()` | `show`, `edit`, `update`, `destroy` | | `Route::apiSingleton(...)` | `show`, `update` | | `Route::apiSingleton(...)->creatable()` | `store`, `show`, `update`, `destroy` | `singletons([...])` and `apiSingletons([...])` register several at once, and the pending registration supports `only()`, `except()`, `names()`, `parameters()` and `middleware()` like a normal resource. ## Generating the controller ```bash php artisan make:controller BookCoverController --singleton php artisan make:controller BookCoverController --singleton --creatable ``` With `--singleton` alone, the stub still contains `create`, `store` and `destroy`, each typed `never` and calling `abort(404)`, so they fail loudly if something routes to them; `--creatable` replaces those bodies with empty placeholders. `--api` switches to the API variant, and `--parent=Book` generates a nested singleton controller that type-hints the parent. ## Judgement calls 1. **Is there really only one?** If a book might later have several images, a nested resource (`books.images`) avoids a migration of your URL design. 2. **Who identifies it?** For user-owned singletons, derive the record from the authenticated user so one member cannot address another's preferences. 3. **Should it be creatable?** If the record is created automatically (a profile row at registration), leave `creatable()` off and keep the surface smaller. 4. **Does deletion make sense?** Use `destroyable()` when users may remove it but not create it through this controller. ## Common mistakes - **Using `Route::resource` with `only(['show', 'edit', 'update'])`** for a one-of-a-kind record, which still puts an id in the URL that clients must invent or leak. - **Trusting an id in a hand-written singleton URL** such as `/members/{member}/preferences`, which lets one member address another's record unless a policy checks it. - **Forgetting `creatable()`** and then wondering why `POST /books/{book}/cover` returns 405: the default singleton has no store route. - **Implementing the stubbed `create`, `store` and `destroy`** without registering them; the methods exist in the generated class but no route reaches them until the registration opts in.

  • How does a controller for Route::singleton('preferences', ...) know whose preferences to show?
    The URI has no identifier, so the controller derives the record from context, typically the authenticated user: `$request->user()->preferences`. Applying `auth` middleware to the singleton makes that user available and stops anonymous access.
  • What is the difference between creatable() and destroyable() on a singleton?
    `creatable()` adds `create`, `store` and `destroy`, for a singleton users can create through this controller. `destroyable()` adds only `destroy`, for one created elsewhere, such as at registration, that users may still remove.

saying these in an interview costs you the question

  • A singleton resource registers index and show without an id.
  • Route::singleton registers create and store by default.
  • apiSingleton registers the same routes as singleton but returns JSON.
  • Singleton resources cannot be nested under a normal resource.
  • destroyable() adds create, store and destroy.