Using Generated Server Routes Wrappers
Apical TS supports two contract-first server integration styles:
- Wrapper-based integrations via
--server - Metadata-driven integrations via
--routes
This page focuses on the first style. It is the approach used in
examples/express: Apical generates typed wrappers, and you explicitly bind
them to your framework routes. If you want to derive a framework layer from
route metadata instead of wiring routes one by one, see the Hono example and the
--routes flow.
How to Generate Server Wrappers
To generate server-side wrappers, use the CLI with the --server flag:
npx @apical-ts/craft generate \
--server \
-i https://petstore.swagger.io/v2/swagger.json \
-o generated
This creates:
routes/: route metadata shared by every generated integrationserver/index.ts: exports the server handler wrappers and typesserver/<operationId>.ts: individual operation handler wrappers
When to Use --server vs --routes
- Use
--serverwhen you want Apical to validate requests and type your handler inputs/outputs, while you keep explicit control over framework route registration. This is the more static style shown inexamples/express. - Use
--routeswhen you want metadata only and plan to generate a second framework layer from it. This is the more dynamic style shown inexamples/hono. - You can mix both approaches because
--serveralso generatesroutes/. The same contract can therefore power Express handlers, Hono generators, MSW mock handlers, or React Query hook generators without remodeling the API surface.
Runtime Dependencies
The generated server code requires zod as a runtime dependency for request and
response validation. After generation, install dependencies in your project or
output directory as needed.
Using the Wrapped Handler
The generated route wrapper takes a typed handler and returns an async function that you can connect to any web framework. Your framework adapter is responsible for:
- Extracting
query,path,headers,body, andcontentType - Passing that data to the wrapper
- Translating the wrapper result back into the framework response
This is what keeps the framework layer thin: the contract stays in the generated files, while your adapter only moves data in and out of them.
Example usage with Express:
const getPetByIdHandler: getPetByIdHandler = async (params) => {
if (!params.isValid) {
return { status: "400" };
}
const { petId } = params.value.path;
const pet = mockPets.find((candidate) => candidate.id === petId);
if (!pet) {
return { status: "404" };
}
return {
status: "200",
contentType: "application/json",
data: pet,
};
};
createExpressAdapter(getPetByIdRoute(), getPetByIdHandler)(app);
The wrapper receives a single params object containing validated query, path, headers, and body data, or structured validation errors when parsing fails. All responses are type checked against the OpenAPI contract.
Handler Function Signature
The handler you provide to the wrapper receives one of:
{ isValid: true, value: { query, path, headers, body, ... } }{ isValid: false, kind: "query-error" | "body-error" | ..., error: ZodError }
It must return a typed object shaped like { status, contentType, data }.
Why This Is the "Static" Route Style
With --server, you still choose how routes are mounted in the framework. That
is useful when you want:
- full control over middleware ordering and framework features
- custom business logic per endpoint
- incremental adoption in an existing server
If, instead, you want the contract to drive route registration itself, start
from --routes and generate a framework-specific layer from the route metadata,
as shown in examples/hono.