Documentation

Error handling

Throw a structured error from any handler and Vura turns it into a clean JSON response — with the right status code, a stable error code, and details that are safe in production. @celsian/vura-core gives you the HttpError class, a set of factory helpers, and dev/production formatting.

// src/api/orders/[id].ts
import { notFound } from '@celsian/vura-core';

export async function GET(req: any) {
  const order = await db.orders.find(req.params.id);
  if (!order) throw notFound('Order not found');
  return order;
}

The thrown error becomes:

{ "error": "Order not found", "code": "NOT_FOUND" }

with a 404 status.


HttpError

The core error type. Throw it directly, or use a factory for the common cases.

new HttpError(statusCode: number, code: string, message: string, details?: unknown)
import { HttpError } from '@celsian/vura-core';

throw new HttpError(409, 'CONFLICT', 'Email already registered', { email });
Property Type Description
statusCode number HTTP status sent to the client.
code string Stable machine-readable code (see ErrorCode). Any string is allowed.
message string Human-readable message.
details unknown Optional structured context. Surfaced only in development (see below).

Factory helpers

Shorthands for the common statuses — each returns an HttpError you throw:

import { badRequest, unauthorized, forbidden, notFound } from '@celsian/vura-core';

throw badRequest('Missing "email" field');
throw unauthorized();
throw forbidden('Not your resource');
Helper Status Code
badRequest(message?, details?) 400 BAD_REQUEST
unauthorized(message?, details?) 401 UNAUTHORIZED
forbidden(message?, details?) 403 FORBIDDEN
notFound(message?, details?) 404 NOT_FOUND
methodNotAllowed(message?, details?) 405 METHOD_NOT_ALLOWED
conflict(message?, details?) 409 CONFLICT
rateLimited(message?, details?) 429 RATE_LIMITED
internalError(message?, details?) 500 INTERNAL_ERROR
serviceUnavailable(message?, details?) 503 SERVICE_UNAVAILABLE

Every argument is optional — unauthorized() defaults to the message 'Unauthorized'.


Dev vs. production responses

Error bodies are sanitized in production so you never leak internals. The mode comes from NODE_ENV (production → production mode, anything else → development).

Development — full detail for debugging:

{
  "error": "Database connection refused",
  "code": "INTERNAL_ERROR",
  "statusCode": 500,
  "details": { "host": "db.internal" },
  "stack": "Error: Database connection refused\n    at ..."
}

Production5xx messages are replaced with a generic string; no details, no stack:

{ "error": "Internal Server Error", "code": "INTERNAL_ERROR" }

4xx errors keep their message in both modes — they're client-facing by design (a 404 message is meant to be read). Only 5xx messages are masked in production, since those can carry internal detail.


Error codes

ErrorCode is a map of predefined codes you can reference instead of typing string literals. The set spans request errors (VALIDATION_ERROR, BAD_REQUEST, UNAUTHORIZED, FORBIDDEN, NOT_FOUND, METHOD_NOT_ALLOWED, CONFLICT, RATE_LIMITED, PAYLOAD_TOO_LARGE), server errors (INTERNAL_ERROR, NOT_IMPLEMENTED, BAD_GATEWAY, SERVICE_UNAVAILABLE, TIMEOUT), and framework errors (RENDER_ERROR, HANDLER_ERROR, HOOK_ERROR, CONFIG_ERROR). Codes are extensible — any string works as a code.

import { HttpError, ErrorCode } from '@celsian/vura-core';

throw new HttpError(413, ErrorCode.PAYLOAD_TOO_LARGE, 'File exceeds 10 MB');

Reporting to an error service

To forward uncaught errors to Sentry or similar, register a global error handler. It runs in addition to the normal error response — it doesn't replace it.

import { setGlobalErrorHandler, reportError, getLogger } from '@celsian/vura-core';

setGlobalErrorHandler((error, context) => {
  Sentry.captureException(error, { extra: context });
});

reportError(error, context?, logger?) logs an error and invokes the registered handler (a no-op if none is set). It's what an onError hook typically calls:

// src/api/_hooks.ts
export const onError = [
  (error, req) => reportError(error, { path: req.url }, getLogger()),
];

Utilities

Function Purpose
formatErrorResponse(error, mode?) Turn any Error into { statusCode, body }, applying dev/prod rules.
sendErrorResponse(reply, error, mode?) Format and send the error through a reply.
getErrorMode() 'development' or 'production', from NODE_ENV.
renderErrorPage(error, options?) Render an HTML error page (dev shows the stack; prod shows a generic page). Accepts a customHandler that can return HTML or null to fall back.
setGlobalErrorHandler(fn) / getGlobalErrorHandler() Set / read the global reporting handler.
reportError(error, context?, logger?) Log and forward an error to the global handler.