15. Pattern: Singleton

The Singleton pattern is used when a resource has exactly one instance in a given context (such as globally, per tenant, or per user).

Instead of treating the resource as a collection requiring an identifier (e.g., /users/123/profile), you expose it as a direct endpoint without an ID (e.g., /profile or /settings).

15.1. Overview

Singleton simplifies the API design by removing the collection wrapper and identifier from the path when only one instance makes logical sense.

Typical examples:

  • GET /profile (The authenticated user’s profile)
  • GET /config (Global system configuration)
  • PATCH /account/billing-preferences (Tenant-specific billing settings)

These operations:

  • Remove the burden of discovering or storing an ID from the client
  • Rely on context (like an authentication token or API key) to scope the resource
  • Typically support only GET, PUT, and PATCH methods

15.2. When to Use Singleton

Use Singleton when:

  • The resource is logically unique in the current scope.
    • e.g., A user only has one profile or wallet.
  • The resource identifier is redundant or derived entirely from authentication context.
    • e.g., Using /me instead of forcing the client to decode a JWT to find their user ID for /users/{id}.
  • The resource implicitly exists upon creation of a parent resource.
    • e.g., When a user is registered, their settings are generated with default values.
  • You want to prevent clients from creating multiple instances.
    • Without a POST collection endpoint, the cardinality is strictly limited to one.

15.3. When NOT to Use Singleton

Avoid Singleton when:

  • There is a possibility of multiple instances in the future.
    • If a user might eventually need multiple profiles or multiple addresses, use a standard collection (/addresses/{id}) from the start.
  • You need to reference the resource from other entities by ID.
    • If the singleton needs a distinct ID to be referenced elsewhere, it might still need to exist as a collection item, or the ID needs to be explicitly returned in the singleton representation.
  • The resource can be cleanly deleted and re-created frequently, mimicking a collection lifecycle rather than a persistent single state.

15.4. What the Pattern Looks Like

The Singleton pattern modifies standard CRUD by omitting the collection endpoints and stripping the ID from the URL path.

Collection CRUD (For Comparison)

  • POST /settings → Create new settings
  • GET /settings/{id} → Read settings
  • PATCH /settings/{id} → Update settings

Singleton Resource

  • GET /settings
    • Returns the single settings object directly (not an array).
  • PUT /settings
    • Replaces the entire singleton resource. If it doesn’t exist yet, this creates it.
  • PATCH /settings
    • Partially updates the singleton resource.

Notice that POST and DELETE are typically omitted. You do not “create” a singleton (it inherently exists or is created via PUT), and you typically do not “delete” it (though you might revert it to defaults).

15.5. Anti-Patterns to Avoid

1. Returning a single-item array

GET /settings

[
  {
    "theme": "dark",
    "notifications": true
  }
]
  • A singleton is not a collection. Returning an array of one item forces the client to always extract the first element. Return the object directly.

2. Requiring a static or hardcoded ID

GET /settings/1
  • If the ID is always 1, default, or global, remove the ID from the path. It provides no value and complicates the URI.

3. Using POST to update the Singleton

POST /settings
{
  "theme": "light"
}
  • POST is traditionally for appending to a collection or triggering processing. Use PUT (for replacement) or PATCH (for partial updates) to modify a singleton.

15.15. OpenAPI Example

A lean but complete OpenAPI 3.0.3 document showing:

  • A Singleton /profile endpoint.
  • Contextual derivation (no ID required).
  • GET and PATCH methods.
openapi: 3.0.3
info:
  title: User API - Singleton Pattern Example
  version: 1.0.0
servers:
  - url: https://api.example.com

paths:
  /profile:
    get:
      summary: Get the current user's profile
      description: Returns the singleton profile for the authenticated user.
      tags: [Profile]
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Profile'
              examples:
                default:
                  value:
                    displayName: "Jane Doe"
                    email: "jane@example.com"
                    timezone: "America/Denver"
                    
    patch:
      summary: Update the current user's profile
      description: Partially updates the singleton profile resource.
      tags: [Profile]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ProfileUpdateRequest'
            examples:
              default:
                value:
                  timezone: "America/New_York"
      responses:
        '200':
          description: Profile updated successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Profile'
              examples:
                default:
                  value:
                    displayName: "Jane Doe"
                    email: "jane@example.com"
                    timezone: "America/New_York"

components:
  schemas:
    Profile:
      type: object
      properties:
        displayName:
          type: string
        email:
          type: string
          format: email
        timezone:
          type: string
          
    ProfileUpdateRequest:
      type: object
      properties:
        displayName:
          type: string
        timezone:
          type: string

15.7. Visualizing Singleton (Mermaid Diagram)

sequenceDiagram
    participant Client
    participant API
    participant Database

    Note over Client,API: GET Singleton (No ID needed)
    Client->>API: GET /profile (Auth: Bearer JWT)
    API->>Database: Query Profile by context (userId from JWT)
    Database-->>API: Return Profile Object
    API-->>Client: 200 OK { displayName: "Jane" }

    Note over Client,API: Update Singleton
    Client->>API: PATCH /profile { timezone: "UTC" }
    API->>Database: Update Profile for contextual user
    Database-->>API: Acknowledge Update
    API-->>Client: 200 OK { ...updated profile }