Outgoing responses can be validated too. Use the response property to declare the expected status code, content type, and body schema:
import type { User } from "~/types/api-payload";
import { defineRoute } from "_/api";
export default defineRoute<"users">(({ GET }) => [
GET<{
response: [200, "json", User],
}>(async (ctx) => {
// response must comply with the defined schema
}),
]);Before sending, KosmoJS checks that the actual status, content type, and body match the schema. If anything is off - a missing field, a type mismatch, a constraint violation - it throws a ValidationError instead of sending malformed data to the client.
Development vs Production
Response validation is environment-aware:
- Development / test: every declared response schema is validated at runtime. Opt out per handler with runtimeValidation: false.
- Production builds: response validation is disabled by default. To enable it running in production, set
runtimeValidation: trueon the response target:
export default defineRoute<"users">(({ GET }) => [
GET<{
response: [200, "json", User],
},
{
response: { // validate this response in production too
runtimeValidation: true,
}
}>(async (ctx) => {
// ...
}),
]);There is no global switch - each handler enables production response validation for itself. This is deliberate: validating every outgoing response costs CPU on your hottest path, so production validation is a per-endpoint decision, not a blanket default.
Note the asymmetry with request validation: payloads and parameters coming into your API are always validated, in every environment (unless explicitly skipped) - they cross a trust boundary. Responses are produced by your own code, so by default they are checked only where bugs are cheap: in development.
Response validation is especially valuable for data sourced from databases or third-party APIs, where the shape can change without warning. If an endpoint serves such data and malformed output would be worse than a thrown error, that endpoint is a candidate for runtimeValidation: true.
Defining a response schema also enables automatic OpenAPI derivation - type safety and documentation in one step. Details ›
Overriding the Default
Response validation runs in the validate:response slot. Claim that slot and your middleware decides what a valid response looks like. The built-in check stops running.
Use it for responses no schema can describe - a stream, for example. Details ›