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.
| Suffix | Meaning |
|---|---|
*RouteFragment | Reusable path segment |
*QueryParams | GET/PATCH query object |
*RequestBody | POST/PUT/PATCH/DELETE body |
List*Route | Collection GET |
Get*Route | Single-entity GET |
Create*Route, Edit*Route, Delete*Route | Writes |