CORE CONCEPTS
Validation
Rasengan Server integrates @rasenganjs/validators directly — attach a schema to a route and validation middleware is injected automatically, ahead of your handler.
import z from "zod"; routes(router: Router) { router.post("/users", this.create, { body: z.object({ name: z.string() }) }); }
If validation fails, the handler never runs — the request short-circuits with a 400 JSON error response by default.
Global Configuration
import { zodAdapter } from '@rasenganjs/validators'; bootstrap((app) => { app.configureValidation({ adapter: zodAdapter, // this is already the default onError: (errors, ctx) => Response.json({ code: 'VALIDATION_ERROR', errors }, { status: 422 }), }); });
ServerApp defaults to zodAdapter and a 400 JSON error handler — you only need configureValidation() to change the adapter or customize the error response shape/status.
What Gets Validated
interface SchemaDefinition { body?: ZodSchema; params?: ZodSchema; query?: ZodSchema; onError?: ( errors: ValidationError[], ctx: Context ) => Response | Promise<Response>; // per-route override }
router.put('/users/:id', this.update, { params: z.object({ id: z.coerce.number() }), query: z.object({ notify: z.coerce.boolean().optional() }), body: z.object({ name: z.string().optional() }), });
params— validated data is merged intoctx.paramsin place.query— validated data is available viactx.get('validatedQuery')(not merged intoctx.queryitself).body—ctx.bodyis replaced with the validated/coerced data (and also stored underctx.get('parsedBody')).
Unlike params and body, a validated query schema's output does not
overwrite ctx.query — read it via ctx.get('validatedQuery') instead. Easy
to miss if you're used to params/body's in-place behavior.
Per-route Error Override
router.post('/users', this.create, { body: CreateUserSchema, onError: (errors) => Response.json({ errors }, { status: 422 }), });
A schema's own onError takes priority over the global configureValidation({ onError }) handler for that one route.
Controller-level Schemas
Declaring schemas once on the controller and referencing them by method name avoids repeating the schema at every router.post(...) call — see Controllers & Routing for the full pattern, including why the schema is still passed as a trailing argument for TypeScript inference even though the runtime also matches it by method name.
