Skip to main content

OpenAPI schema helpers

The json* helpers build chanfana responses objects that document the same envelope the c.respond* helpers produce. They return plain objects keyed by status code, so you can spread several into one responses map.

src/modules/users/user-update.route.ts
import {contentJson, OpenAPIRoute} from 'chanfana';
import type {Context} from 'hono';
import {z} from 'zod';
import {jsonUpdateResponses} from '@ltv/cwb';

const User = z.object({id: z.string(), name: z.string(), email: z.email()});
const UserPatch = User.omit({id: true}).partial();

export class UserUpdate extends OpenAPIRoute {
schema = {
tags: ['Users'],
request: {params: z.object({id: z.string()}), body: contentJson(UserPatch)},
responses: jsonUpdateResponses('User', User), // 200, 400, 401, 404, 422, 500
};

async handle(c: Context) {
const {params, body} = await this.getValidatedData<typeof this.schema>();
return c.respond({id: params.id, name: 'Jane', email: 'jane@example.com', ...body});
}
}
zod schemas only

Schema arguments must be zod schemas (TSchema extends z.ZodType). Passing a plain object such as {id: 'string'} is a compile error, because it would produce a broken OpenAPI document.

Success responses

HelperStatusDocuments
jsonSuccess(description, schema)200{success: true, data: schema, meta}
jsonCreated(description, schema)201same envelope
jsonAccepted(description, schema)202same envelope
jsonNoContent(description)204no body
jsonPaginated(description, schema)200data: schema[] (the helper wraps the item schema in an array) and a required meta.pagination

Error responses

Each takes an optional description and documents {success: false, error: ErrorSchema, meta}.

HelperStatusDefault description
jsonBadRequest()400Bad Request
jsonUnauthorized()401Unauthorized
jsonForbidden()403Forbidden
jsonNotFound()404Not Found
jsonConflict()409Conflict
jsonValidationError()422Validation Error; error.code is VALIDATION_ERROR and details is an array of {field, message, code?}
jsonInternalError()500Internal Server Error

The 422 shape matches what the error handler returns for chanfana request validation failures:

{
"success": false,
"error": {
"id": "k3x9m2p8q1w7e4r6t5y0u2i8",
"code": "VALIDATION_ERROR",
"message": "Validation failed",
"details": [{"field": "body.email", "message": "Invalid email address", "code": "7001"}]
},
"meta": {"timestamp": 1757829600000, "requestId": "tz4a98xxat96iws9zmbrgj3a"}
}

CRUD presets

Presets combine a success response with the errors a typical endpoint can return. The resource name is used in descriptions such as User retrieved successfully.

PresetSuccessErrors
jsonCrudResponses(resource, schema) (get one)200400, 401, 404, 500
jsonListResponses(resource, schema) (paginated list)200 via jsonPaginated400, 401, 500
jsonCreateResponses(resource, schema)201400, 401, 409, 422, 500
jsonUpdateResponses(resource, schema)200400, 401, 404, 422, 500
jsonDeleteResponses(resource)204400, 401, 404, 500
Pass the item schema to list helpers

jsonListResponses and jsonPaginated wrap schema in z.array() themselves. Pass the schema of one item (User), not z.array(User), or the document describes an array of arrays.

Combining and custom responses

Spread helpers to build any combination. jsonCustomResponse(statusCode, description, schema?) documents a status without adding the envelope:

import {z} from 'zod';
import {jsonCustomResponse, jsonForbidden, jsonNotFound, jsonSuccess} from '@ltv/cwb';

const Report = z.object({id: z.string(), url: z.url()});

const responses = {
...jsonSuccess('Report ready', Report),
...jsonCustomResponse('202', 'Report is still being generated'),
...jsonForbidden('Reports require the analyst role'),
...jsonNotFound('Report not found'),
...jsonCustomResponse('429', 'Rate limited', z.object({retryAfter: z.number()})),
};

Exported schemas

Use these zod schemas to build your own envelope-shaped responses or to validate responses in tests.

SchemaFields
MetaSchematimestamp?, requestId?, traceId?, pagination?
PaginationMetaSchemapage, limit, total, totalPages (integers), hasNext, hasPrev (booleans)
ErrorSchemaid, code, message, details?
tests/users.test.ts
import {z} from 'zod';
import {MetaSchema, PaginationMetaSchema} from '@ltv/cwb';

const UserListBody = z.object({
success: z.literal(true),
data: z.array(z.object({id: z.string(), name: z.string()})),
meta: MetaSchema.extend({pagination: PaginationMetaSchema}),
});

UserListBody.parse(await response.json());

Request schemas

The helpers cover responses only. Describe requests with chanfana and zod as usual, for example request: {body: contentJson(Input), query: z.object({...})}. Validation failures are turned into the 422 envelope by the error handler, in both fromHono modes (default and passthroughErrors: true).