Manejo avanzado de errores HTTP siguiendo el estándar RFC 9457 "Problem Details for HTTP APIs"
Este paquete proporciona clases de error especializadas y middleware para Express que implementan completamente el estándar RFC 9457, ofreciendo respuestas de error estructuradas, ricas en información y compatibles internacionalmente.
- ¿Qué hay de nuevo en v2.0.0?
- RFC 9457 - Problem Details
- Instalación
- Uso Básico
- Clases de Error Disponibles
- Middleware para Express
- Ejemplos Avanzados
- Migración desde v1.x
- API Reference
La versión 2.0.0 es una reescritura completa que implementa el estándar RFC 9457:
- ✅ Nueva clase base
ProblemDetailsErrorcon campos estándar RFC 9457 - ✅ Middleware automático para Express (
problemDetailsHandler) - ✅ Clase
VALIDATION_ERRORpara errores de validación complejos - ✅ Campos de extensión personalizables (
extra,errors) - ✅ URLs de tipo configurables para documentación
- ✅ Timestamps automáticos y campos
instance - ✅ Compatibilidad total con RFC 9457
RFC 9457 define un formato estándar para describir errores en APIs HTTP. En lugar de respuestas simples:
❌ ANTES (Error simple):
{
"error": "User not found",
"code": 404
}✅ AHORA (RFC 9457):
{
"type": "https://httpstatuses.io/404",
"title": "Not Found",
"status": 404,
"detail": "El usuario con ID '123' no existe en el sistema",
"instance": "/users/123",
"extra": {
"resourceType": "user",
"resourceId": "123",
"suggestions": [
{
"action": "list_users",
"url": "/users",
"description": "Ver todos los usuarios disponibles"
}
]
},
"timestamp": "2023-10-15T10:30:00.123Z"
}npm install apicustomerrorsRequisitos:
- Node.js 16+
- Proyecto con
"type": "module"enpackage.json
import {
ProblemDetailsError,
BAD_REQUEST_ERROR,
NOT_FOUND_ERROR,
UNAUTHORIZED_ERROR,
VALIDATION_ERROR,
problemDetailsHandler
} from 'apicustomerrors';import express from 'express';
import { NOT_FOUND_ERROR, problemDetailsHandler } from 'apicustomerrors';
const app = express();
app.get('/users/:id', (req, res, next) => {
const user = database.findUser(req.params.id);
if (!user) {
const error = new NOT_FOUND_ERROR({
detail: `Usuario con ID '${req.params.id}' no encontrado`,
extra: {
searchedAt: new Date().toISOString(),
resourceType: 'user',
resourceId: req.params.id
}
});
return next(error);
}
res.json(user);
});
// ¡IMPORTANTE! Agregar el middleware al final
app.use(problemDetailsHandler());
app.listen(3000);GET /users/999HTTP/1.1 404 Not Found
Content-Type: application/problem+json
{
"type": "https://httpstatuses.io/404",
"title": "Not Found",
"status": 404,
"detail": "Usuario con ID '999' no encontrado",
"instance": "/users/999",
"extra": {
"searchedAt": "2023-10-15T10:30:00.123Z",
"resourceType": "user",
"resourceId": "999"
},
"timestamp": "2023-10-15T10:30:00.123Z"
}| Clase | Status | Descripción | Nuevo en v2.0 |
|---|---|---|---|
BAD_REQUEST_ERROR |
400 | Solicitud malformada | ⬆️ |
UNAUTHORIZED_ERROR |
401 | No autorizado | ⬆️ |
FORBIDDEN_ERROR |
403 | Prohibido | ⬆️ |
NOT_FOUND_ERROR |
404 | Recurso no encontrado | ⬆️ |
METHOD_NOT_ALLOWED_ERROR |
405 | Método no permitido | ⬆️ |
CONFLICT_ERROR |
409 | Conflicto | ⬆️ |
UNSUPPORTED_MEDIA_TYPE_ERROR |
415 | Tipo de medio no soportado | ⬆️ |
VALIDATION_ERROR |
422 | Errores de validación | ✅ NUEVO |
TOO_MANY_REQUESTS_ERROR |
429 | Demasiadas solicitudes | ⬆️ |
INTERNAL_SERVER_ERROR |
500 | Error interno del servidor | ⬆️ |
SERVICE_UNAVAILABLE_ERROR |
503 | Servicio no disponible | ⬆️ |
ProblemDetailsError |
Personalizable | Clase base para errores personalizados | ✅ NUEVO |
Leyenda: ⬆️ = Actualizada con RFC 9457 | ✅ = Nueva clase
import { problemDetailsHandler } from 'apicustomerrors';
// Agregar AL FINAL de todos los middlewares
app.use(problemDetailsHandler());- ✅ Detección automática de errores
ProblemDetailsError - ✅ Asignación automática del campo
instanceconreq.originalUrl - ✅ Respuesta JSON con Content-Type correcto
- ✅ Conversión automática de errores nativos a Problem Details
import express from 'express';
import {
BAD_REQUEST_ERROR,
UNAUTHORIZED_ERROR,
VALIDATION_ERROR,
problemDetailsHandler
} from 'apicustomerrors';
const app = express();
app.use(express.json());
// Ruta con validación
app.post('/users', (req, res, next) => {
const { name, email, age } = req.body;
const errors = [];
if (!name) {
errors.push({
field: 'name',
message: 'El nombre es obligatorio',
pointer: '#/name'
});
}
if (!email || !/\S+@\S+\.\S+/.test(email)) {
errors.push({
field: 'email',
message: 'Email válido requerido',
pointer: '#/email'
});
}
if (errors.length > 0) {
const error = new VALIDATION_ERROR({
detail: `Se encontraron ${errors.length} errores de validación`,
errors: errors,
extra: {
totalErrors: errors.length,
providedFields: Object.keys(req.body)
}
});
return next(error);
}
res.status(201).json({ message: 'Usuario creado' });
});
// Ruta protegida
app.get('/admin', (req, res, next) => {
if (!req.headers.authorization) {
const error = new UNAUTHORIZED_ERROR({
detail: 'Token de autorización requerido',
extra: {
requiredHeader: 'Authorization',
authTypes: ['Bearer', 'Basic'],
loginUrl: 'https://api.example.com/login'
}
});
return next(error);
}
res.json({ message: 'Panel administrativo' });
});
// Middleware de manejo de errores (AL FINAL)
app.use(problemDetailsHandler());import { ProblemDetailsError } from 'apicustomerrors';
const businessRuleError = new ProblemDetailsError({
type: "https://api.miempresa.com/problems/insufficient-funds",
title: "Insufficient Funds",
status: 402,
detail: "Su saldo actual no es suficiente para esta transacción",
instance: "/transactions/tx-12345",
errors: [
{
rule: "minimum-balance",
message: "Saldo mínimo requerido: $10.00",
currentBalance: 5.50,
requiredBalance: 10.00
}
],
extra: {
accountId: "acc-67890",
transactionAmount: 25.00,
currentBalance: 5.50,
suggestedActions: [
{
action: "add_funds",
url: "https://api.miempresa.com/billing/add-funds",
description: "Agregar fondos a su cuenta"
},
{
action: "contact_support",
phone: "+1-800-123-4567",
email: "support@miempresa.com",
description: "Contactar soporte para asistencia"
}
]
}
});
// Usar en Express
app.post('/transactions', (req, res, next) => {
if (insufficientFunds()) {
return next(businessRuleError);
}
// ... lógica de transacción
});import { VALIDATION_ERROR } from 'apicustomerrors';
const validationError = new VALIDATION_ERROR({
detail: "Los datos del formulario contienen errores múltiples",
errors: [
{
field: "email",
message: "Formato de email inválido",
providedValue: "invalid-email",
pointer: "#/contact/email",
rule: "email-format"
},
{
field: "age",
message: "La edad debe estar entre 18 y 100 años",
providedValue: 15,
pointer: "#/personal/age",
rule: "age-range",
constraints: { min: 18, max: 100 }
},
{
field: "password",
message: "La contraseña debe tener al menos 8 caracteres",
providedValue: "[HIDDEN]",
pointer: "#/security/password",
rule: "password-strength"
}
],
extra: {
totalErrors: 3,
validationEngine: "joi-validator-v2.1",
requestTimestamp: new Date().toISOString(),
clientInfo: {
userAgent: "Mozilla/5.0...",
ipAddress: "192.168.1.100"
}
}
});import { BAD_REQUEST_ERROR } from 'apicustomerrors';
const apiError = new BAD_REQUEST_ERROR({
type: "https://docs.miapi.com/errors/invalid-request-format",
detail: "El formato de la solicitud no es válido para este endpoint",
extra: {
expectedFormat: "application/json",
receivedFormat: "text/plain",
documentation: {
apiReference: "https://docs.miapi.com/api/reference",
examples: "https://docs.miapi.com/api/examples",
support: "https://support.miapi.com"
},
correctExample: {
"Content-Type": "application/json",
"body": {
"name": "string",
"email": "string"
}
}
}
});❌ ANTES (v1.x):
const error = new NOT_FOUND_ERROR("Usuario no encontrado");
console.log(error.statusCode); // 404✅ AHORA (v2.0.0):
const error = new NOT_FOUND_ERROR({
detail: "Usuario no encontrado",
extra: { userId: "123" }
});
console.log(error.status); // 404
console.log(error.toJSON()); // Formato RFC 9457 completoAgregar al final de tus middlewares:
import { problemDetailsHandler } from 'apicustomerrors';
// Todas tus rutas...
// AL FINAL - Middleware de manejo de errores
app.use(problemDetailsHandler());| v1.x | v2.0.0 | Cambio |
|---|---|---|
new ERROR("mensaje") |
new ERROR({ detail: "mensaje" }) |
Constructor con objeto |
error.statusCode |
error.status |
Propiedad renombrada |
error.message |
error.detail |
Campo RFC 9457 |
| Sin middleware | problemDetailsHandler() |
Middleware requerido |
| Error simple | error.toJSON() |
Formato RFC 9457 |
Clase base que implementa RFC 9457.
new ProblemDetailsError({
type, // string - URI que identifica el tipo de problema
title, // string - Resumen corto del problema
status, // number - Código de estado HTTP
detail, // string - Descripción específica del problema
instance, // string - URI de esta ocurrencia específica
errors, // array - Lista de errores específicos
extra // object - Información adicional personalizada
})| Campo | Tipo | Requerido | Descripción |
|---|---|---|---|
type |
string | No | URI que identifica el tipo de problema |
title |
string | No | Resumen corto y legible del problema |
status |
number | Sí | Código de estado HTTP |
detail |
string | No | Explicación específica de esta ocurrencia |
instance |
string | No | URI que identifica esta ocurrencia específica |
timestamp |
string | Auto | ISO timestamp de cuando ocurrió el error |
| Campo | Tipo | Descripción |
|---|---|---|
errors |
array | Lista de errores específicos (útil para validación) |
extra |
object | Información adicional personalizada |
Retorna el error en formato RFC 9457.
const error = new NOT_FOUND_ERROR({
detail: "Recurso no encontrado",
extra: { resourceId: "123" }
});
console.log(error.toJSON());
// {
// "type": "https://httpstatuses.io/404",
// "title": "Not Found",
// "status": 404,
// "detail": "Recurso no encontrado",
// "extra": { "resourceId": "123" },
// "timestamp": "2023-10-15T10:30:00.123Z"
// }Middleware para Express que maneja automáticamente errores RFC 9457.
import { problemDetailsHandler } from 'apicustomerrors';
app.use(problemDetailsHandler());Funcionalidades:
- Detecta instancias de
ProblemDetailsError - Asigna automáticamente
req.originalUrlainstance - Establece el Content-Type correcto
- Convierte errores nativos a formato Problem Details
POST /users
Content-Type: application/json
{
"name": "",
"email": "invalid-email",
"age": -5
}HTTP/1.1 422 Unprocessable Entity
Content-Type: application/problem+json
{
"type": "https://httpstatuses.io/422",
"title": "Unprocessable Entity",
"status": 422,
"detail": "Se encontraron 3 errores de validación en los datos proporcionados",
"instance": "/users",
"errors": [
{
"field": "name",
"message": "El nombre no puede estar vacío",
"providedValue": "",
"pointer": "#/name"
},
{
"field": "email",
"message": "Formato de email inválido",
"providedValue": "invalid-email",
"pointer": "#/email"
},
{
"field": "age",
"message": "La edad debe ser un número positivo",
"providedValue": -5,
"pointer": "#/age"
}
],
"extra": {
"totalErrors": 3,
"validationEngine": "custom-validator",
"requestId": "req-1634567890123"
},
"timestamp": "2023-10-15T10:30:00.123Z"
}HTTP/1.1 402 Payment Required
Content-Type: application/problem+json
{
"type": "https://api.example.com/problems/insufficient-funds",
"title": "Insufficient Funds",
"status": 402,
"detail": "Su saldo actual ($5.50) no es suficiente para esta transacción ($25.00)",
"instance": "/transactions/tx-12345",
"extra": {
"currentBalance": 5.50,
"requiredAmount": 25.00,
"accountId": "acc-67890",
"suggestedActions": [
{
"action": "add_funds",
"url": "/billing/add-funds",
"description": "Agregar fondos a su cuenta"
},
{
"action": "contact_support",
"phone": "+1-800-123-4567",
"description": "Contactar soporte para asistencia"
}
]
},
"timestamp": "2023-10-15T10:30:00.123Z"
}¡Las contribuciones son bienvenidas!
- Fork el repositorio
- Crea una rama para tu feature (
git checkout -b feature/amazing-feature) - Commit tus cambios (
git commit -m 'Add amazing feature') - Push a la rama (
git push origin feature/amazing-feature) - Abre un Pull Request
Este proyecto está bajo la Licencia ISC. Ver el archivo LICENSE para más detalles.
- RFC 9457 - Problem Details for HTTP APIs
- HTTP Status Codes Reference
- Repositorio en GitHub
- Paquete en NPM
- GitHub Issues: Crear Issue
- Email: Contactar
¡Desarrolla APIs con manejo de errores profesional y estándar! 🚀