alepha@docs:~/docs/guides/server$
cat 5-error-handling.md
2 min read
Last commit:

#Error Handling

Alepha maps errors to HTTP responses through the HttpError class. Unhandled errors become 500 Internal Server Error.

#HttpError

Throw an HttpError to return a specific HTTP status code and message:

typescript
1import { HttpError } from "alepha/server";2 3throw new HttpError({ status: 404, message: "Product not found" });

#Named Error Classes

Alepha provides error subclasses for common HTTP statuses:

typescript
 1import { 2  BadRequestError, 3  UnauthorizedError, 4  ForbiddenError, 5  NotFoundError, 6  ConflictError, 7  ValidationError, 8} from "alepha/server"; 9 10throw new NotFoundError("User not found");11throw new BadRequestError("Invalid input");12throw new UnauthorizedError("Not authenticated");13throw new ForbiddenError("Access denied");14throw new ConflictError("Entity already exists");15throw new ValidationError("Validation has failed");

Each class has a sensible default message:

Class Status Default Message
BadRequestError 400 "Invalid request body"
ValidationError 400 "Validation has failed"
UnauthorizedError 401 "Not allowed to access this resource"
ForbiddenError 403 "No permission to access this resource"
NotFoundError 404 "Resource not found"
ConflictError 409 "Entity already exists"

#Error Identification

Check whether an error is an HTTP error, optionally filtering by status:

typescript
1import { HttpError } from "alepha/server";2 3if (HttpError.is(error)) {4  // any HTTP error5}6 7if (HttpError.is(error, 404)) {8  // specifically a 4049}

#Error Chaining

Pass a cause to preserve the original error context:

typescript
1try {2  await externalService.call();3} catch (error) {4  throw new HttpError(5    { status: 502, message: "Upstream service failed" },6    error,7  );8}

The cause is included in the error JSON response under the cause field:

json
1{2  "error": "BadGatewayError",3  "status": 502,4  "message": "Upstream service failed",5  "cause": {6    "name": "FetchError",7    "message": "Connection refused"8  }9}

#Error Response Format

All HTTP errors are serialized as JSON with a consistent structure:

json
1{2  "error": "NotFoundError",3  "status": 404,4  "message": "User not found",5  "requestId": "abc-123"6}

The error field is the class name (e.g. NotFoundError, ConflictError). For plain HttpError instances, it is derived from the status code.

#Status Code Mapping

HttpError maps status codes to error names automatically:

Status Error Name
400 BadRequestError
401 UnauthorizedError
403 ForbiddenError
404 NotFoundError
409 ConflictError
413 PayloadTooLargeError
429 TooManyRequestsError
500 InternalServerError
502 BadGatewayError
503 ServiceUnavailableError

#Global Error Handling

Use the server:onError hook to intercept errors across all routes:

typescript
 1import { $hook } from "alepha"; 2import { $logger } from "alepha/logger"; 3  4class ErrorHandler { 5  log = $logger(); 6  7  onError = $hook({ 8    on: "server:onError", 9    handler: async ({ error, request }) => {10      this.log.error("Request failed", {11        error,12        path: request.url.pathname,13        method: request.method,14      });15    },16  });17}

The server:onError hook fires after the error is caught but before the response is sent. The response body is reset at this point and can be modified via request.reply.

#Schema Validation Errors

When a request fails schema validation (invalid body, params, or query), Alepha throws a ValidationError (status 400) automatically. You do not need to validate request data manually.