Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/BladimirGS/judicial-backend/llms.txt

Use this file to discover all available pages before exploring further.

Judicial Backend uses a unified error-handling pipeline built on three layers: the AppError class that represents any anticipated failure, the asyncHandler wrapper that captures rejections from async controllers, and a global Express error middleware that classifies, logs, and serializes every error into a consistent JSON response. This approach ensures that no error reaches the client in an ambiguous or information-leaking form.

The AppError Class

AppError extends the native Error class and is the canonical way to signal expected failures — invalid input, missing records, unauthorized access, and similar scenarios. Because it is thrown intentionally, it is treated as an operational error: the server is functioning correctly, and the error is a legitimate response to a bad request or a missing resource.
export class AppError extends Error {
    public readonly statusCode: number;
    public readonly code: string;
    public readonly details?: unknown;

    constructor(message: string, statusCode = 500, code = 'INTERNAL_ERROR', details?: unknown) {
        super(message);

        this.statusCode = statusCode;
        this.code = code;
        this.details = details;

        Object.setPrototypeOf(this, AppError.prototype);
    }
}
FieldTypeDescription
messagestringHuman-readable description of the error (shown to the client for 4xx only).
statusCodenumberHTTP status code; defaults to 500.
codestringMachine-readable identifier, e.g. APELACION_NOT_FOUND; defaults to INTERNAL_ERROR.
detailsunknownOptional extra context (validation fields, debug hints). Optional.

Throwing an AppError in a service

throw new AppError('Apelación no encontrada', 404, 'APELACION_NOT_FOUND');
The three arguments map directly to message, statusCode, and code. Pass a fourth argument when you need structured detail:
throw new AppError('Parámetros inválidos', 400, 'BAD_REQUEST', { campo: 'idMateria' });

Operational vs Critical Errors

CategorySourceLogged asClient message
OperationalAppError instancewarn (4xx) / error (5xx)Real message (4xx) or generic (5xx)
CriticalUnhandled Error or unknown valueerror"Error interno del servidor"
Unhandled, non-operational errors never expose stack traces or internal details to the client in any environment. The stack is logged server-side via Pino, but the HTTP response always returns the generic string "Error interno del servidor" with a 500 status.

asyncHandler Wrapper

Every async controller must be wrapped with asyncHandler so that rejected promises are forwarded to Express’s error middleware rather than causing an unhandled rejection crash.
type AsyncController = (req: Request, res: Response, next: NextFunction) => Promise<unknown>;

export const asyncHandler = (fn: AsyncController) => {
    return (req: Request, res: Response, next: NextFunction) => {
        return Promise.resolve(fn(req, res, next)).catch(next);
    };
};
Usage in a route module:
import { asyncHandler } from '../../../core/handlers/async-handler';

export const ApelacionController = {
    create: asyncHandler(async (req: Request, res: Response) => {
        const nuevaApelacion = await ApelacionService.create(req.body);
        return responseUtil.created(res, { id: nuevaApelacion?.id });
    }),
};
Any throw inside the wrapped function — including throw new AppError(...) — is caught by .catch(next) and passed to the global error handler automatically.

globalErrorHandler Middleware

Registered as the last middleware in the Express application, globalErrorHandler is the single exit point for all errors.
export const globalErrorHandler = (err: unknown, _req: Request, res: Response, _next: NextFunction) => {
    // Operational errors (AppError)
    if (err instanceof AppError) {
        if (err.statusCode < 500) {
            logger.warn({ code: err.code, message: err.message, details: err.details });
        } else {
            logger.error({ code: err.code, message: err.message, stack: err.stack });
        }

        if (err.statusCode === 400 && err.details) {
            return responseUtil.validationError(res, err.details);
        }

        const safeMessage = err.statusCode >= 500 ? 'Error interno del servidor' : err.message;
        return responseUtil.error(res, safeMessage, err.statusCode);
    }

    // Critical / unknown errors
    if (err instanceof Error) {
        logger.error({ type: err.name, message: err.message, stack: err.stack });
    } else {
        logger.error({ message: 'Unknown error', error: err });
    }

    return responseUtil.error(res, 'Error interno del servidor', 500);
};
The classification logic works as follows:
  1. AppError 4xx — logs a warn with code, message, and details; sends the real message to the client.
  2. AppError 5xx — logs an error with the stack; replaces the message with the generic fallback before sending.
  3. AppError 400 + details — delegates to responseUtil.validationError for a structured validation response.
  4. Native Error — logs name, message, and stack; returns a 500 generic response.
  5. Unknown value — logs the raw value; returns a 500 generic response.

responseUtil Helpers

All HTTP responses are produced through responseUtil to guarantee a consistent shape. The available helpers are:
// 200 OK
responseUtil.success<T>(res: Response, data: T, message = 'Operación exitosa')

// 201 Created
responseUtil.created<T>(res: Response, data: T, message = 'Recurso creado')

Standard Error Response Shape

All error responses follow this JSON envelope:
{
  "status": "error",
  "message": "Human-readable error message",
  "code": "ERROR_CODE"
}
When responseUtil.validationError is used (HTTP 400 with structured details), the body gains an errors array:
{
  "status": "error",
  "code": "VALIDATION_ERROR",
  "message": "Los datos enviados son inválidos",
  "errors": [
    { "campo": "idMateria", "error": "La materia es requerida" }
  ]
}
The responseUtil.error helper sets code to "INTERNAL_SERVER_ERROR" for status 500, or "ERROR" for any other status code.

Build docs developers (and LLMs) love