Defining an API

Defining routes

How to write route files: path fragments, query/body types, and route aliases.

Create or extend contracts/<api>/routes/<domain>.ts in this order.

1. Path fragments

Reusable segments with typed :params:

export type WidgetRouteFragment = RouteFragment<"/widget">;
export type IdentifyWidgetRouteFragment = RouteFragment<"/widget/:id", { id: string }>;
export type WidgetTagsRouteFragment = CombineRouteFragments<
  IdentifyWidgetRouteFragment,
  RouteFragment<"/tags">
>;
// → path: "/widget/:id/tags", pathParams: { id: string }

2. Query and body helpers

Define query params and request bodies as separate type aliases:

export type WidgetListQueryParams = {
  name?: string;
  ids?: string[];
};

export type CreateWidgetRequestBody = {
  name: string;
  enabled: boolean;
};

3. Route aliases

Wire method, path, query, body, and response together:

export type ListWidgetRoute = Route<
  "get",
  WidgetRouteFragment,
  WidgetListQueryParams,
  {},
  CollectionSuccess<WidgetEntity>
>;

export type GetWidgetRoute = Route<
  "get",
  IdentifyWidgetRouteFragment,
  {},
  {},
  Success<WidgetEntity>
>;

export type CreateWidgetRoute = Route<
  "post",
  WidgetRouteFragment,
  {},
  CreateWidgetRequestBody,
  Success<WidgetEntity>
>;

4. Entity types

Put response shapes in models/<domain>.ts when they are non-trivial:

export type WidgetData = { name: string; enabled: boolean };
export type WidgetEntity = {
  type: "widget"
  id: string
  data: WidgetData
}

Reuse global types from ~/types/* where possible instead of duplicating shapes.

Naming conventions

It is recommended that you follow these conventions to make your definitions more organized.

SuffixMeaning
*RouteFragmentReusable path segment
*QueryParamsGET/PATCH query object
*RequestBodyPOST/PUT/PATCH/DELETE body
List*RouteCollection GET
Get*RouteSingle-entity GET
Create*Route, Edit*Route, Delete*RouteWrites
Copyright © 2026