Remote Eaze
Backend

Error Handling

Centralized error handling strategy, standard error codes, and developer guidelines.

Remote Eaze uses a centralized, standardized error handling mechanism to ensure consistent API responses, security, and observability.

Philosophy

  • Fail Fast: Validate inputs immediately (Zod) and throw errors early.
  • Fail Safe: Never crash the server. The Global Error Handler catches everything.
  • Fail Secure: Never leak stack traces or internal database details to the client in production.
  • Fail Loudly (Internally): Log 500s with full context (request ID, URL, stack) for debugging.

Standard Error Response Format

All API errors (4xx and 5xx) follow this JSON structure:

{
  "code": "NOT_FOUND",
  "message": "Resource not found",
  "statusCode": 404,
  "meta": { ... } // Optional — present on ValidationError, RateLimitError, etc.
}

This shape is produced by AppError.toJSON() in apps/api/src/errors/app-error.ts and matches the ApiErrorResponse interface in @remote-eaze/shared.

Error Codes

Error codes are defined as a single source of truth in packages/shared/src/api/error-codes.ts and used by both the API (throwing) and the web app (handling).

StatusCodeError ClassNotes
400BAD_REQUESTBadRequestErrorGeneric client error.
400VALIDATION_ERRORValidationErrorZod/schema validation failed. meta.errors contains [{ field, message }].
401UNAUTHORIZEDUnauthorizedErrorMissing or invalid session.
403FORBIDDENForbiddenErrorAuthenticated but lacks permission.
404NOT_FOUNDNotFoundErrorResource does not exist.
409CONFLICTConflictErrorDuplicate resource (unique constraint).
429RATE_LIMIT_EXCEEDEDRateLimitErrorToo many requests. meta.retryAfter may be present.
500INTERNAL_SERVER_ERRORAppError (base)Unexpected crash. Message hidden in production.
503SERVICE_UNAVAILABLEServiceUnavailableErrorDependency down (Redis/DB). meta.service may be present.

How to Throw Errors

Do not throw generic Error objects. Use the typed classes from apps/api/src/errors/app-error.ts.

Wrong

if (!user) throw new Error("User not found");
// Result: 500 Internal Server Error (confusing, leaks message in dev)
import { NotFoundError } from "../../errors/app-error";

if (!user) throw new NotFoundError("User not found");
// Result: 404 { code: "NOT_FOUND", message: "User not found", statusCode: 404 }

ValidationError (With Structured Errors)

import { ValidationError } from "../../errors/app-error";

throw new ValidationError("Invalid request data", [
  { field: "email", message: "Required" },
  { field: "name", message: "Must be at least 2 characters" },
]);
// Result: 400 { code: "VALIDATION_ERROR", ..., meta: { errors: [...] } }

RateLimitError (With Retry Header)

import { RateLimitError } from "../../errors/app-error";

throw new RateLimitError("Too many requests", 60);
// Result: 429 { ..., meta: { retryAfter: 60 } }

Global Error Handler

The Global Error Handler (plugins/global-error.ts) is a Fastify plugin that intercepts all thrown errors and normalizes them into the standard response format. It processes errors in this priority order:

1. Custom AppError Instances

Any error extending AppError is serialized via .toJSON(). Errors with statusCode >= 500 are logged as error; below 500 logged as info.

2. Zod Validation Errors

Zod errors (using .issues for Zod 4 compatibility) are converted into ValidationError with structured meta.errors:

{
  "code": "VALIDATION_ERROR",
  "message": "Invalid request data",
  "statusCode": 400,
  "meta": {
    "errors": [
      { "field": "amount", "message": "Expected number, received string" }
    ]
  }
}

3. Fastify Native Validation Errors

Schema validation from Fastify's built-in validator (e.g., JSON Schema) is normalized into the same ValidationError shape.

4. Fastify 404s

Route-not-found errors are wrapped in NotFoundError.

5. Prisma Database Errors

Known Prisma errors are mapped to appropriate HTTP responses:

Prisma CodeDescriptionHTTP StatusError Class
P2002Unique constraint violation409ConflictError — includes the target field name
P2025Record not found404NotFoundError
P2003Foreign key constraint violation400BadRequestError — "Invalid reference to related record"
P2014Required relation missing400BadRequestError — "Required relation is missing"

6. Database Connection Failures

PrismaClientInitializationError is caught and returned as 503 Service Unavailable. Logged at fatal level.

7. Unknown/Crash Errors

Anything not matching the above is treated as an unhandled crash:

  • Logged as error with full context (requestId, method, url, stack).
  • Returns 500 with a generic message in production (no stack trace leak). In development, the original error.message is returned.

Rule

Do not wrap your route handlers in try/catch unless you are performing a specific rollback, audit logging a failure, or custom recovery logic. Let errors bubble up to the Global Handler.

Logging Behavior

Error TypeLog LevelContext
AppError (4xx)infoError object
AppError (5xx)errorError object
Prisma known errorserrorFull Prisma error
DB connection failurefatalFull error
Unknown crasheserrorrequestId, method, url, stack

On this page