API Client
Ship type‑safe client definition with zero runtime cost. Generate your OpenAPI typings from your CrumbJS routes and consume them on the frontend (or other backends) with first‑class DX.
One source of truth → your schemas.
Why this approach?
- Single source of truth: Your Zod/OpenAPI schemas define requests/responses; types sync automatically.
- End‑to‑end type safety: Strong typing for params, bodies, success and error shapes.
- Zero runtime codegen: We emit
.d.ts
during development; production builds don’t pay extra cost. - Better DX: Autocomplete for paths, params and response shapes. Safer refactors.
- Predictable errors: Document and type your validation errors once (
spec.invalid
), handle them everywhere. - Framework‑agnostic clients: Works with any frontend (Nuxt, Next, Vite, etc.) and any fetch layer.
- Generate vs Infer reasons
Enable feature
In your main App (usually src/index.ts
), enable client schema generation when serving:
app.serve({
generateClientSchema: true,
});
What it does
- Emits
ROOT/client.d.ts
(typings for your API) only whenmode = development
andwithOpenapi = true
. Disabled in production by default. - Adds a few extra milliseconds to startup. For example, ~60ms with ~200 endpoints on a typical dev machine. Numbers are indicative; your mileage may vary.
Example
Server side
Define request/response schemas. They will flow into the generated client typings:
import { z, spec } from "@crumbjs/core";
// The schema will be used to validate `query` and to describe possible invalid responses
const querySchema = z.object({ name: z.string().min(3) });
app.get(
"/hello-by-query",
({ query }) => ({ message: `Hello ${query.name}` }),
{
query: querySchema,
responses: [
spec.response(200, z.object({ message: z.string() })), // client success type
spec.invalid(querySchema), // client error type (auto-generated field errors from this schema)
],
}
);
Generated client types (simplified):
const data:
| {
message: string;
}
| undefined;
const error:
| {
status: number;
message: string;
fields: {
name?: string[];
};
}
| undefined;
Client side
Install the client helpers:
npm i openapi-fetch
npm i -D openapi-typescript typescript
Use the generated types with openapi-fetch
:
import createClient from "openapi-fetch";
// In this example, `@api` is a tsconfig path alias pointing to the CrumbJS server root.
// You can also import relatively (e.g., "../../server/client.d.ts") if you prefer.
import type { paths } from "@api/client";
const client = createClient<paths>({ baseUrl: "http://localhost:8081" });
const { data, error } = await client.GET("/api/hello-by-query", {
params: {
query: { name: "CrumbJS" },
},
});
if (error) {
console.error(error);
/**
* {
* status: 400,
* message: "Invalid Query",
* fields: {
* name: ["Too small: expected string to have >=3 characters"],
* },
* }
*/
}
console.log(data);
/**
* { message: "Hello CrumbJS" }
*/
Troubleshooting
Cannot find module '@api/client'
→ Add a path alias in your tsconfig or import relatively.fetch
/CORS issues → Ensurecors()
middleware is enabled/configured in your server.- Out‑of‑date types → Regenerate by restarting dev server (types are only emitted in dev +
withOpenapi=true
).
Why generate a client schema (vs inferring from App
instance like other frameworks)?
- No style lock-in: Fluent routes are supported—but never required.
- No type spaghetti: Avoids complex merges; keeps @crumbjs/core flat, flexible, and maintainable.
- Happier IDE/TS: Less deep instantiation/inference overhead.
- Single, portable contract: one file for the entire API—diff-friendly and PR-readable.
- Zero runtime cost: generated in dev/build; nothing shipped to prod.
FAQ
Can I use Axios instead of openapi-fetch
?
Yes. The typings are OpenAPI‑flavored; you can use any HTTP client and still enjoy type safety.
Does this work for other backends (BFF/microservices)?
Absolutely. Import client.d.ts
into any TypeScript project and call your API with typed requests/responses.
Why .d.ts
and not a generated client implementation?
We optimize for DX and zero runtime cost. Keep control over your fetch layer while enjoying full types.