<!-- Markdown mirror of https://justscale.sh/docs/http/typed-params -->

# 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` 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

```typescript
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

```typescript
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

```typescript
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

```typescript
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.

## Next Steps

- Path Parameters
- Server-Sent Events
- Guards
