Response Validation
BurgerAPI can also check what your handler returns. Declare a response schema and BurgerAPI validates the handler's output after it runs, helping you catch contract drift and bugs early. Only JSON responses are checked: application/json and application/*+json (for example application/vnd.api+json) are validated. Other content types are skipped.
Example
import { z } from "zod";
export const GET = {
response: { 200: z.object({ ok: z.boolean() }) },
};
export function GET() {
return Response.json({ ok: true });
}
Modes
Response validation has three modes, set app-wide in configuration under responseValidation, or per route in that route's config.ts:
dev(default): observe only. In development, a mismatch is logged to the console but the response is returned unchanged. Nothing breaks.enforce: a mismatch is a server bug. In production the client gets a genericapplication/problem+json500(detail: "Internal Server Error", no issues or stack); in development the body includes the issues. If the handler itself returned422, that status is kept.afterRouteandmapResponsestill run on the error response, so response headers such as CORS are preserved.off: never validate responses.
The app-wide mode is the validation.responseValidation server option. A route's config.ts can override it:
export default {
responseValidation: "enforce",
};
The default dev mode means adding a response schema is free and safe: existing apps that declare none are unaffected, and apps that declare one get helpful feedback without risking a broken response.
Status selection
BurgerAPI picks the schema by status code: an exact code first (200), then a class (2xx). So response: { "2xx": ... } covers every success status. A key that is neither a status code nor a class, such as good, can never be selected, so it fails at startup with an error naming the route, method and key. Use a status code ("200") or a lowercase status class ("2xx").
When to use it
- Catching a handler that returns the wrong shape.
- Documenting the contract your route promises.
- Enforcing strict contracts in
enforcemode for production-critical routes.
For full RFC-style error documents on request failures, see Problem Details.
Types for this feature
The response schema describes the shape your handler should return. It is checked at runtime (in dev mode it only logs); the type of the handler itself is still Response, so the schema's output type is not connected to the handler return type.
The types you use (from burger-api):
RouteSchema: a route's full schema map (for programmatic routes)z.infer<typeof GET.response["200"]>: the documented response shape, for reuse
✅ Correct. Declare the response shape, and reuse its type for helper functions:
import { z } from "zod";
export const GET = { response: { 200: z.object({ ok: z.boolean() }) } };
import { z } from "zod";
import type { GET } from "./schema";
type StatusResponse = z.infer<typeof GET.response["200"]>; // { ok: boolean }
The runtime check happens after the handler; for strict typing of what the handler produces, keep the schema and handler in the same route directory and pass the schema export to defineRoute. See the TypeScript overview.
Check your code: bun run typecheck.