Handling Validation Errors
Understand built-in ValidationError (422) handling and customize envelopes with middleware try/catch — they bypass defineTaser().onError().
When incoming request parameters, query strings, headers, or JSON bodies fail validation, Taser.js throws a ValidationError. The runtime converts it to a 422 JSON response automatically.
Validation errors bypass `.onError()`
ValidationError (422) and ResponseValidationError (response contracts, 500) are handled by
built-in runtime handlers. They never reach defineTaser().onError(), which is reserved for
unhandled server crashes. Customize validation envelopes in middleware instead.
Anatomy of a ValidationError
The ValidationError class exposes the raw issues array returned by your schema validator:
class ValidationError extends Error {
readonly issues: readonly StandardSchemaV1.Issue[];
}Each issue typically contains:
path: Array of property keys or array indices indicating where the error occurred (for example,["body", "email"]or["query", "page"]).message: Human-readable error description (for example,"Invalid email address").
Default 422 Envelope
Without customization, failed input validation returns:
{
"errors": [
{ "message": "Number must be greater than or equal to 1", "path": ["page"] },
{ "message": "Invalid enum value", "path": ["category"] }
]
}Customizing Validation Envelopes in Middleware
Catch ValidationError in a layout middleware try/catch before it reaches the built-in handler:
import { ValidationError, middleware, t } from "@taserjs/router";
import { unprocessable } from "@taserjs/router/reply";
const formatValidationErrors = middleware(async (_ctx, next) => {
try {
return await next();
} catch (error) {
if (error instanceof ValidationError) {
const formattedErrors: Record<string, string[]> = {};
for (const issue of error.issues) {
const fieldName = issue.path?.map(String).join(".") || "root";
if (!formattedErrors[fieldName]) {
formattedErrors[fieldName] = [];
}
formattedErrors[fieldName].push(issue.message);
}
return unprocessable({
status: 422,
message: "The given data was invalid.",
errors: formattedErrors,
});
}
throw error;
}
});
export default t.layout("/*").use(formatValidationErrors);Example Custom Error Response
Given a request with invalid query parameters:
curl "http://localhost:3000/products?page=-5&category=unknown"With the middleware above, the client receives:
{
"status": 422,
"message": "The given data was invalid.",
"errors": {
"query.page": ["Number must be greater than or equal to 1"],
"query.category": ["Invalid enum value. Expected 'electronics' | 'clothing' | 'books'"]
}
}Auto-Generated 422 Response Schemas
When you attach input schemas (query, params, body) to a route, Taser.js automatically documents a 422 error contract for that endpoint. The typed client SDK also understands that the route can return 422 validation issues.