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 match —
types: { product: Product }matches:product - Lowercased class name —
types: { Product }matches:product - Lowercased + "Ref" suffix —
types: { 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 :productRefMultiple 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
Referencefor the matching model - Attaches the database resolver so
awaitfetches 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.