Responses & Errors

Global Error Handling

Catch unhandled exceptions and route misses centrally using .onError() and .notFound(). Standardize error envelopes across your entire API.

Taser.js features a centralized error handling architecture configured on the createTaserApp() builder in src/taser.ts.


Centralized onError Boundary

Any uncaught exception thrown during middleware execution, input validation, or route handlers is passed directly to .onError():

src/taser.ts
import { createTaserApp, ValidationError, type InferAppContext } from "@taserjs/router";
import { internalServerError, notFound, unprocessableEntity } 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 createTaserApp()
  .context(context)
  .onError((error, ctx) => {
    // 1. Handle Schema Validation Errors
    if (error instanceof ValidationError) {
      return unprocessableEntity({
        status: 422,
        error: "Unprocessable Entity",
        issues: error.issues,
      });
    }

    // 2. Handle Custom Domain Exceptions
    if (error instanceof ResourceNotFoundError) {
      return notFound({
        status: 404,
        error: "Not Found",
        message: error.message,
      });
    }

    // 3. Handle Unexpected Server Exceptions
    console.error(`[Error on ${ctx?.method} ${ctx?.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,
    });
  });

Customizing 404 Not Found Handling

Use .notFound() to define a standardized response when an incoming request does not match any registered route:

src/taser.ts
import { notFound } from "@taserjs/router/reply";

export default createTaserApp()
  .context(context)
  .notFound((ctx) => {
    return notFound({
      status: 404,
      error: "Not Found",
      message: `The endpoint ${ctx.method} ${ctx.path} does not exist on this server.`,
    });
  });

Throwing Errors in Route Handlers

Because .onError() catches all unhandled exceptions, you can throw errors directly inside services or handlers without manual try/catch boilerplate:

src/routes/teams/$id.get.ts
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) => {
  const team = await ctx.db.findTeam(ctx.params.id);

  if (!team) {
    // Automatically caught by .onError() and mapped to 404 JSON response:
    throw new ResourceNotFoundError("Team");
  }

  return json(team);
});