Concepts
Contract Kit is organized around a small set of boundaries. The goal is to define the shape of your API once, then reuse that definition across the server, client, validation, docs, and optional application architecture.
Contracts
A contract describes one HTTP endpoint: method, path, path parameters, query parameters, request body, response statuses, response bodies, and metadata.
Contracts are the source of truth for:
- Server request validation
- Server route-owned response validation
- Client request and response types
- React Query keys and options
- React Hook Form body validation
- OpenAPI generation when schemas are Zod
Server runtime
The server runtime turns a contract into a route handler. Incoming data is parsed and validated before your handler runs. Handler responses are validated before they are sent when the contract declares response schemas.
export const GET = server.route(getTodo).handle(async ({ ctx, path }) => {
const todo = await ctx.ports.db.todos.findById(path.id);
if (!todo) {
return {
status: 404,
body: { code: "TODO_NOT_FOUND", message: "Todo not found" },
};
}
return { status: 200, body: todo };
});
Response ownership
Contract Kit separates responses into three groups:
- Route-owned responses are business responses returned by the handler or thrown as
AppErrorfrom the handler. They are validated againstcontract.responses. - Framework-owned responses come from request parsing, request validation, hooks, unmatched routes, global error handling, or contract violations. They skip route response validation and use Contract Kit's standard
{ code, message, details? }envelope when applicable. - Transport-owned responses are native
Responseobjects returned from handlers or hooks. They bypass JSON response validation andbeforeSend, and are useful for non-JSON responses, redirects, streaming, and Server-Sent Events.
This keeps the contract strict for business outcomes without forcing every route to declare infrastructure responses such as auth failures, CORS preflights, malformed JSON, or unexpected failures.
Hooks
Hooks are ordered lifecycle functions for infrastructure behavior. Use them for concerns that should not live inside every route handler: auth, CORS, rate limits, logging, tracing, response shaping, and error mapping.
Hooks can short-circuit before the handler, enrich context, observe responses, or map errors.
Ports and providers
Ports are the dependency interface your app code uses through ctx.ports. Providers are production adapters that install or replace ports at server startup.
export const server = await createNextServer({
ports,
providers: [loggerPinoProvider, redisProvider],
createContext: ({ ports }) => ({ ports }),
});
Tests can pass mock ports directly, while production can install Redis, Pino, Better Auth, Turso, Inngest, mail providers, and other adapters.
Client calls
The client has two request styles:
call()returns the response body for 2xx responses and throwsContractErrorfor failures.safeCall()returns{ ok: true, data }or{ ok: false, error }.
Use call() with React Query and most UI data-fetching flows. Use safeCall() when explicit branching reads better than exceptions.
Application and domain
The application package defines reusable use cases with input/output validation. Use it when the same business operation should run from HTTP routes, jobs, scripts, tests, or event handlers.
The domain package provides optional helpers for value objects, entities, and domain event declarations. Use it when you want more structure around core business concepts.