Error Handling#

Built-in HTTP error classes and custom error handler. Throw errors in route handlers and middlewares - Buntok catches them automatically and returns proper JSON responses. You do NOT need try/catch in every handler.

Built-in Error Classes#

ClassStatusUsage
BadRequestError400Invalid request data
UnauthorizedError401Authentication required
ForbiddenError403Access denied
NotFoundError404Resource not found
MethodNotAllowedError405HTTP method not allowed
ConflictError409Resource conflict (duplicate, etc.)
UnprocessableEntityError422Business logic validation
TooManyRequestsError429Rate limit exceeded
InternalServerError500Server error
ServiceUnavailableError503Service temporarily down

Usage#

typescript
import {
  NotFoundError,
  ForbiddenError,
  ConflictError,
  BadRequestError,
} from "@buntok/core/helpers";

app.get("/users/:id", async (ctx) => {
  const user = await db.user.findUnique({ where: { id: ctx.params.id } });
  if (!user) throw new NotFoundError("User not found");
  return ctx.json(user);
});

app.delete("/users/:id", async (ctx) => {
  if (!isAdmin) throw new ForbiddenError("Admin only");
  await db.user.delete({ where: { id: ctx.params.id } });
  return ctx.json({ deleted: true });
});

app.post("/users", async (ctx) => {
  const existing = await db.user.findUnique({ where: { email } });
  if (existing) throw new ConflictError("Email already registered");
  // ...
});

Error Response Format#

typescript
// All HttpError classes return this format:
{
  "success": false,
  "error": "NotFound",        // Error name (camelCase → PascalCase)
  "message": "User not found" // Your custom message
}
Production message masking
In production (NODE_ENV=production), the default handler always shows the message of a 4xx HttpError - it was thrown on purpose (e.g. NotFoundError("User not found")). Everything else - 5xx HttpError and unexpected (non-HttpError) errors - is replaced with "An unexpected error occurred" so internal details never leak. Outside production, the original message is always shown.

Custom Error Handler#

Override the default error handler to customize error responses:

typescript
const app = new Buntok();

app.onError((error, ctx) => {
  console.error(error);

  // Custom error format
  return ctx.json({
    error: {
      name: error.name,
      message: error.message,
      stack: process.env.NODE_ENV === "development" ? error.stack : undefined,
    },
  }, error instanceof HttpError ? error.status : 500);
});

Custom Error Classes#

Extend HttpError for domain-specific errors:

typescript
import { HttpError } from "@buntok/core/helpers";

// Extend HttpError for custom errors
class ValidationError extends HttpError {
  constructor(message: string) {
    super(422, message);
    this.name = "ValidationError";
  }
}

class InsufficientBalanceError extends HttpError {
  constructor(required: number, available: number) {
    super(400, `Insufficient balance: need ${required}, have ${available}`);
    this.name = "InsufficientBalanceError";
  }
}

// Usage
app.post("/transfer", async (ctx) => {
  const { amount } = await ctx.body();
  const balance = await getBalance(ctx.user.id);

  if (amount > balance) {
    throw new InsufficientBalanceError(amount, balance);
  }
  // ...
});

Try/Catch#

You can also catch errors manually for custom handling:

typescript
app.post("/payment", async (ctx) => {
  try {
    const result = await processPayment(await ctx.body());
    return ctx.json({ success: true, ...result });
  } catch (error) {
    if (error instanceof HttpError) {
      return ctx.json({ error: error.message }, error.status);
    }
    return ctx.json({ error: "Payment failed" }, 500);
  }
});
Info
You don't need try/catch for most cases. Just throw an error class and Buntok handles the response automatically.

Next Steps#