Global Error Handling
Catch unhandled runtime crashes with .onError() and customize 404s with .notFound(). Protocol errors (422/415) and response contracts bypass onError.
Taser.js configures error handling on the defineTaser() builder in src/taser.ts. The runnable Hono instance that applies these handlers is compiled inside src/.taserjs/routes.gen.ts.
What .onError() Receives
.onError() is dedicated to unhandled runtime server crashes. It runs only after built-in protocol handlers have already short-circuited:
| Error | Default status | Reaches .onError()? |
|---|---|---|
ValidationError (input schemas) | 422 | No — built-in JSON envelope |
ResponseValidationError (.returns() contracts) | 500 | No — built-in JSON envelope |
UnsupportedMediaTypeError | 415 | No — built-in JSON envelope |
Thrown Response | as thrown | No — returned as-is |
Everything else (domain Error, unexpected throws) | your handler or 500 | Yes |
To customize 422 validation or response-contract envelopes, catch them in middleware with try/catch (see Handling Validation Errors). Do not expect those errors inside .onError().
Centralized onError Boundary
import { defineTaser } from "@taserjs/router";
import { internalServerError, notFound } from "@taserjs/router/reply";
import { context } from "./context.js";
export class ResourceNotFoundError extends Error {
constructor(public resource: string) {
super(`${resource} not found`);
this.name = "ResourceNotFoundError";
}
}
export default defineTaser()
.context(context)
.onError((error, req) => {
// Domain exceptions thrown from handlers / middleware:
if (error instanceof ResourceNotFoundError) {
return notFound({
status: 404,
error: "Not Found",
message: error.message,
});
}
console.error(`[Error on ${req.method} ${req.path}]:`, error);
const isProduction = process.env.NODE_ENV === "production";
return internalServerError({
status: 500,
error: "Internal Server Error",
message: isProduction ? "An unexpected error occurred" : (error as Error).message,
});
});Handler signature
.onError((error, req) => Response) receives a TaserRequest as the second argument (path,
method, params, query, headers, url, raw), not the full handler ctx.
Customizing 404 Not Found Handling
Use .notFound() when an incoming request does not match any registered route:
import { defineTaser } from "@taserjs/router";
import { notFound } from "@taserjs/router/reply";
import { context } from "./context.js";
export default defineTaser()
.context(context)
.notFound(({ req }) => {
return notFound({
status: 404,
error: "Not Found",
message: `The endpoint ${req.method} ${req.path} does not exist on this server.`,
});
});Throwing Errors in Route Handlers
Because .onError() catches unhandled domain and runtime exceptions, you can throw from services or handlers without local try/catch:
import { json } from "@taserjs/router/reply";
import { t } from "@taserjs/router";
import { ResourceNotFoundError } from "../../errors.js";
export default t.get("/teams/:id").handler(async ({ ctx, req }) => {
const team = await ctx.db.findTeam(req.params.id);
if (!team) {
// Caught by .onError() and mapped to your 404 JSON response:
throw new ResourceNotFoundError("Team");
}
return json(team);
});Related Guides
Streaming and Web Payloads
Stream Web ReadableStreams, binary buffers, Blobs, and Server-Sent Events with edge-compatible helpers from @taserjs/router/stream.
CORS Middleware
Configure Cross-Origin Resource Sharing (CORS) using @taserjs/router/cors with static origins, dynamic domain resolvers, and preflight support.