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.
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});
}
}
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
| Helper | Status | Documents |
|---|---|---|
jsonSuccess(description, schema) | 200 | {success: true, data: schema, meta} |
jsonCreated(description, schema) | 201 | same envelope |
jsonAccepted(description, schema) | 202 | same envelope |
jsonNoContent(description) | 204 | no body |
jsonPaginated(description, schema) | 200 | data: 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}.
| Helper | Status | Default description |
|---|---|---|
jsonBadRequest() | 400 | Bad Request |
jsonUnauthorized() | 401 | Unauthorized |
jsonForbidden() | 403 | Forbidden |
jsonNotFound() | 404 | Not Found |
jsonConflict() | 409 | Conflict |
jsonValidationError() | 422 | Validation Error; error.code is VALIDATION_ERROR and details is an array of {field, message, code?} |
jsonInternalError() | 500 | Internal 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.
| Preset | Success | Errors |
|---|---|---|
jsonCrudResponses(resource, schema) (get one) | 200 | 400, 401, 404, 500 |
jsonListResponses(resource, schema) (paginated list) | 200 via jsonPaginated | 400, 401, 500 |
jsonCreateResponses(resource, schema) | 201 | 400, 401, 409, 422, 500 |
jsonUpdateResponses(resource, schema) | 200 | 400, 401, 404, 422, 500 |
jsonDeleteResponses(resource) | 204 | 400, 401, 404, 500 |
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.
| Schema | Fields |
|---|---|
MetaSchema | timestamp?, requestId?, traceId?, pagination? |
PaginationMetaSchema | page, limit, total, totalPages (integers), hasNext, hasPrev (booleans) |
ErrorSchema | id, code, message, details? |
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).