Typed Parameters

Transform path parameters into model references with .types()

Path parameters are strings by default. The .types() method transforms them into model references — awaitable Reference<T> objects that resolve to the actual entity from the database.

Basic Usage

Pass a map of model classes to .types(). Matching path parameters become references automatically:

src/controllers/ticket-controller.tsTypeScript
import { createController } from '@justscale/core';
import { Get, Post } from '@justscale/http';
import { auth } from '@justscale/auth';
import { Ticket } from '../domain/ticket';
import { HelpdeskService } from '../application/helpdesk-service';

export const TicketController = createController('/tickets', {
  inject: { helpdesk: HelpdeskService },

  routes: ({ helpdesk }) => ({
    get: Get('/:ticket')
      .types({ Ticket })
      .use(auth)
      .handle(async ({ params, res }) => {
        // params.ticket is Reference<Ticket> — await to resolve
        const ticket = await params.ticket;
        if (!ticket) return res.status(404).json({ error: 'Not found' });
        res.json(ticket);
      }),

    resolve: Post('/:ticket/resolve')
      .types({ Ticket })
      .use(auth)
      .guard(Ticket.can.resolve)
      .handle(async ({ params, res, user }) => {
        const ticket = await params.ticket;
        if (!ticket) return res.status(404).json({ error: 'Not found' });
        await helpdesk.resolveTicket(ticket, user.name ?? user.email);
        res.status(204).end();
      }),
  }),
});

Matching Rules

.types() matches parameter names using three strategies:

  • Direct matchtypes: { product: Product } matches :product
  • Lowercased class nametypes: { Product } matches :product
  • Lowercased + "Ref" suffixtypes: { Product } matches :productRef
matching-examples.tsTypeScript
import { Get } from '@justscale/http';
import { Product } from './domain';

// All three are equivalent:
Get('/:product').types({ Product })          // matches :product
Get('/:product').types({ product: Product }) // matches :product
Get('/:productRef').types({ Product })       // matches :productRef

Multiple Typed Parameters

Pass multiple models to type several parameters at once. Unmatched parameters stay as strings:

order-controller.tsTypeScript
import { Get } from '@justscale/http';
import { Order, Product } from './domain';

Get('/:order/items/:product/reviews/:reviewId')
  .types({ Order, Product })
  .handle(async ({ params }) => {
    // params.order    → Reference<Order>
    // params.product  → Reference<Product>
    // params.reviewId → string (no matching model)

    const order = await params.order;
    const product = await params.product;
    const reviewId = params.reviewId;
  });

Works with SSE Routes

The .types() method is shared between HTTP and SSE routes:

ticket-events.tsTypeScript
import { SSE } from '@justscale/sse';
import { Ticket } from './domain';

SSE('/:ticket/events')
  .types({ Ticket })
  .handle(async function* ({ params }) {
    const ticket = await params.ticket; // Reference<Ticket>
    if (!ticket) return;

    yield { event: 'connected', data: { subject: ticket.subject } };

    for await (const event of ticket.events) {
      yield { event: event.type, data: event.data };
    }
  });

How It Works

When a request arrives, the framework:

  • Extracts the raw string parameter from the URL (e.g. "prod-123")
  • Creates a fresh Reference for the matching model
  • Attaches the database resolver so await fetches from the database
  • Passes the typed params to your handler
ℹ️

Info

Each request gets a fresh Reference — there is no cross-request caching. Awaiting the same reference twice in one handler returns the cached result.