Route API
primate/route defines HTTP method handlers, validation schemas, and route
hooks. In browser builds, the same route export becomes a typed client for its
server endpoint.
import route from "primate/route";The supporting names on this page describe the inferred public contract; they
are not additional named exports from primate/route. See the
Routing guide for filesystem conventions and walkthroughs.
route()
Normalizes a map of HTTP method handlers for a route file.
function route<R extends RouteHandlers>(handlers: R): Routes<R>;In a browser build, the same call returns the matching endpoint client:
function route<R extends RouteHandlers>(handlers: R): ClientRoute<R>;Parameters
handlers— An object whose keys are supported lowercase HTTP methods and whose values are aRouteHandleror the result ofroute.with().
Returns: A Routes<R> object retaining each method's handler,
options, and inferred page-props type. Primate consumes this object when it
builds the filesystem router.
// routes/user/[id].ts
import route from "primate/route";
export default route({
get(request) {
return `User ${request.path.get("id")}`;
},
});
route.with()
Associates validation and content metadata with one handler while preserving the handler's inferred request and response types.
function with<O extends RouteOptions, R extends ResponseLike>(
options: O,
handler: RouteHandler<O, R>,
): WithResult<O, R>;Parameters
options.contentType— Expected request media type.options.body— Pema schema used to parse and validate the request body.options.path— Pema object or async-object schema for path parameters.options.query— Pema object or async-object schema for query parameters.handler— Function called with aNarrowedRequest<O>.
Returns: A WithResult<O, R> consumed by route().
Throws: If body is present without contentType. At build time, Primate
also rejects path schema keys that do not exactly match the route's dynamic
segments and query schema keys that collide case-insensitively.
import p from "pema";
import route from "primate/route";
export default route({
post: route.with({
contentType: "application/json",
body: p({ name: p.string }),
}, async request => {
const { name } = await request.body.json();
return { name };
}),
});
route.hook()
Types and returns a route hook function unchanged.
function hook(fn: HookFn): HookFn;A hook receives the current request and next. It may return a response
without calling next to stop the chain, or call next(request) and return
its response (or another valid response). A hook must return a
ResponseLike, may call next at most once, and must not call
next and then return undefined.
// routes/admin/+hook.ts
import route from "primate/route";
export default route.hook(async (request, next) => {
if (!request.session.get("user")) return new Response(null, { status: 401 });
return next(request);
});Hook files only. Export
route.hook() from a +hook.js or +hook.ts
file. A build fails if a hook function is exported from an ordinary route
file.
Browser route methods
In a browser build, calling route(handlers) produces a
ClientRoute<R> rather than exposing server handlers. A
generated route import therefore acts as a typed endpoint client. Each HTTP
method calls fetch, substitutes path parameters, appends query values,
serializes the configured body, and resolves with the native Response.
- Non-rest path values are URL-encoded; rest values preserve
/. - Missing optional path values become an empty segment.
- JSON bodies use
JSON.stringify. - URL-encoded bodies use
URLSearchParams. - Multipart requests omit an explicit
Content-Typeheader so the browser can add its boundary. - Caller-supplied headers override the generated content-type header.
import User from "@/routes/user/[id]";
const response = await User.get({
path: { id: "42" },
query: { expand: "profile" },
signal: controller.signal,
});Path and body arguments become required when their route options require them.
A declared query schema also makes query required. Method functions expose
their configured contentType; _result and Page are type-only inference
properties and do not contain runtime values.
Method
Supported lowercase method names.
type Method =
| "get"
| "post"
| "put"
| "delete"
| "head"
| "connect"
| "options"
| "trace"
| "patch";
RouteHandlers
type RouteHandlers = {
[K in Method]?:
| RouteHandler
| WithResult<RouteOptions, ResponseLike>;
};Under the browser export condition, this supporting map is string-indexed so
the transformed module can retain the route source's keys. Server route
construction still accepts only Method keys.
RouteHandler
type RouteHandler<
O extends RouteOptions = RouteOptions,
R extends ResponseLike = ResponseLike,
> = (request: NarrowedRequest<O>) => MaybePromise<R>;Handlers may be synchronous or asynchronous. Thrown errors and rejected
promises enter Primate's route error handling. Pema parse failures become a
400 JSON response; a request content-type mismatch becomes 415.
ResponseLike
Values accepted from route handlers and hooks.
type ResponseLike =
| string
| Dict
| Dict[]
| URL
| Blob
| ReadableStream
| Streamable
| Response
| ResponseFunction<unknown, unknown>
| null;Strings become text responses; dictionaries and arrays become JSON; URLs
become external redirects; binary/streamable values become binary responses;
native Response objects pass through; and null becomes a 204 response.
Response functions are the values produced by helpers from
primate/response.
RouteOptions
type RouteOptions = {
contentType?: MIMEValue;
body?: Parsed<unknown> | AsyncType;
path?: ObjectType | AsyncType;
query?: ObjectType | AsyncType;
};MIMEValue is a value from @rcompat/http's MIME constants. Request-body
method narrowing is provided for JSON, text, URL-encoded, multipart, and
octet-stream content types. Path and query schemas must be object-shaped.
NarrowedRequest
The request type passed to a handler after applying its route schemas.
type NarrowedRequest<O extends RouteOptions> =
Omit<RequestFacade, "body" | "path" | "query"> & {
body: NarrowedBody<O, RequestBody>;
path: NarrowedPath<O>;
query: NarrowedQuery<O>;
};- A
pathschema changesrequest.pathto a bag of its awaited inferred type. - A
queryschema changesrequest.querythe same way. - A
bodyschema changes the matching body reader (json,text,form,multipart, orblob) to return the awaited inferred schema type.
See the RequestFacade API for the complete
request member contract and the Requests guide for examples.
WithResult
The intermediate value returned by route.with().
type WithResult<O extends RouteOptions, R extends ResponseLike> = {
handler: RouteHandler<O, R>;
options: O;
};The runtime value also carries an internal brand used by route().
Under the browser export condition its structural declaration instead includes
an optional result?: R inference property; that property has no runtime
value.
Routes
Maps each input method to its normalized server route method.
type Routes<R extends RouteHandlers> = {
[K in keyof R]: R[K] extends WithResult<
infer O extends RouteOptions,
infer Result
>
? RouteMethodWithResult<O, Result>
: R[K] extends (...args: any[]) => infer Result
? RouteMethodWithResult<Record<never, never>, Result>
: RouteMethod<Record<never, never>>;
};
RouteMethod
type RouteMethod<O extends RouteOptions> = {
handler: (request: NarrowedRequest<O>) => unknown;
options: O;
Page: never;
};
type RouteMethodWithResult<O extends RouteOptions, R> =
Omit<RouteMethod<O>, "Page"> & {
Page: Page<R>;
};handler and options exist at runtime. Page is type-only and extracts the
props accepted by a page response function; its runtime value is absent.
HookFn
type HookFn = (
request: RequestFacade,
next: (request: RequestFacade) => MaybePromise<ResponseLike>,
) => MaybePromise<ResponseLike>;
ClientRoute
The browser form of a route export.
type ClientRoute<R> = {
[K in keyof R]: R[K] extends {
options: infer O extends RouteOptions;
result?: infer Result;
}
? ClientMethod<O, Result>
: R[K] extends (...args: any[]) => infer Result
? ClientMethod<Record<never, never>, Result>
: (args?: RequestMeta<Record<never, never>>) => Promise<Response>;
};
ClientMethod
type ClientMethod<O extends RouteOptions, R = unknown> = {
contentType?: string;
_result?: ClientResult<R>;
Page: Page<R>;
} & ClientMethodFunction<O>;ClientMethodFunction<O> accepts an optional or required object composed from
body, path, and RequestMeta<O>, according to O, and
returns Promise<Response>.
RequestMeta
Browser request options shared by client route methods.
type RequestMeta<O extends RouteOptions> = {
headers?: HeadersInit;
signal?: AbortSignal;
} & (O extends { query: unknown }
? { query: Query<O> | URLSearchParams }
: { query?: Query<O> | URLSearchParams });