intermediate
Validation
Validate untrusted input at service boundaries with schemas, normalization, typed outputs, and clear error responses.
Validate all untrusted input at the service boundary — body, query, params, headers — and produce typed, normalized values handlers can trust.
Schema libraries (Zod, Valibot, Joi, Ajv for JSON Schema) parse and coerce deliberately:
const CreateOrder = z.object({
sku: z.string().min(1),
quantity: z.coerce.number().int().positive().max(100),
});
export async function createOrderHandler(req, res) {
const parsed = CreateOrder.safeParse(req.body);
if (!parsed.success) {
return res.status(400).json({ errors: parsed.error.flatten() });
}
const order = await service.create(parsed.data);
res.status(201).json(order);
}
Validation is not authorization — knowing the shape of input does not mean the caller may perform the action. Return 400 for malformed input, 422 when semantics fail, 403/401 for authz.
On interviews: where validation lives (controller vs domain), how to avoid duplicating DB constraints, and error response consistency.
Common pitfalls: validating only body; trusting `Content-Type`; using validation library types as domain entities without mapping.
The trade-off is balancing simplicity, performance, safety, and operability — name which axis you optimized and what cost you accepted.
Checklist:
- Schemas colocated with route or OpenAPI source.
- safeParse at boundary; throw inside domain only for invariants.
- Normalize (trim, lowercase email) once.
- Separate validation errors from business rule failures.