Desarrollo Backend
Principios Fundamentales
- Servicios sin estado — no almacenes el estado de la sesión en memoria; usa Redis o una base de datos.
- Fallar rápido — valida las entradas en el límite del sistema, devuelve errores temprano.
- Idempotencia — endpoints POST/PUT que se puedan llamar varias veces de forma segura.
- Observabilidad — cada servicio necesita registros (logs), métricas y trazas.
Estructura de Endpoints API
Ciclo de Vida de una Solicitud
Solicitud → Limitación de Tasa → Autenticación → Autorización → Validación → Lógica de Negocio → Respuesta
Nunca te saltes este orden. Autenticación antes que lógica de negocio. Validación antes que procesamiento.
Estándares de Respuesta
// Éxito
{ "data": { ... }, "meta": { "page": 1, "total": 100 } }
// Error
{
"error": {
"code": "VALIDATION_ERROR",
"message": "Descripción legible para humanos",
"details": [{ "field": "email", "issue": "Formato inválido" }]
}
}
Patrones de Autenticación
Mejores Prácticas con JWT
- TTL corto para el token de acceso (15 min) + token de actualización largo (7 días).
- Almacena el token de actualización en una cookie httpOnly (no en localStorage).
- Valida la firma, la expiración, el emisor y la audiencia.
- Rota los tokens de actualización al usarlos.
- Mantén una lista de revocación o usa solo tokens de corta duración.
Autenticación Basada en Sesión
- Almacena el ID de sesión en una cookie httpOnly, Secure, SameSite.
- Almacena los datos de sesión en Redis con TTL.
- Regenera el ID de sesión después del inicio de sesión (prevención de fijación de sesión).
Autorización
// Verifica la autorización en la capa de servicio, no solo en la ruta
async function getOrder(userId: string, orderId: string) {
const order = await db.orders.findById(orderId)
if (!order) throw new NotFoundError()
// Verificación de autorización — el usuario solo puede ver sus propias órdenes
if (order.userId !== userId) throw new ForbiddenError()
return order
}
Trabajos en Segundo Plano y Colas
Usa colas para:
- Envío de correos electrónicos
- Procesamiento de imágenes/archivos
- Llamadas a APIs externas que no necesitan ser síncronas
- Tareas de larga duración
Patrones de Trabajos
Disparar y olvidar: Encolar y devolver inmediatamente
Programado: Ejecutar en un momento específico (cron)
Retrasado: Ejecutar después de X minutos/horas
Reintento: Reintentar en caso de fallo con retroceso (backoff)
Siempre implementa:
- Cola de mensajes fallidos (dead letter queue) para trabajos fallidos
- Clave de idempotencia para evitar procesamiento duplicado
- Tiempo de espera (timeout) por trabajo
- Número máximo de reintentos
Manejo de Errores
// Jerarquía de errores estructurada
class AppError extends Error {
constructor(
public message: string,
public statusCode: number,
public code: string
) { super(message) }
}
class ValidationError extends AppError {
constructor(details: ValidationDetail[]) {
super('Validación fallida', 400, 'VALIDATION_ERROR')
this.details = details
}
}
// Manejador global de errores
app.use((err, req, res, next) => {
if (err instanceof AppError) {
return res.status(err.statusCode).json({ error: err })
}
// Registrar errores inesperados
logger.error('Error inesperado', { err, req })
res.status(500).json({ error: { code: 'INTERNAL_ERROR' } })
})
Lista de Verificación de Middleware
Toda API de producción necesita:
- Registro de solicitudes (método, ruta, estado, duración)
- Limitación de tasa (por IP, por usuario, por endpoint)
- Configuración CORS (lista blanca explícita)
- Helmet / cabeceras de seguridad
- Propagación de ID de solicitud (para trazado distribuido)
- Límites de tamaño del cuerpo
- Manejo de tiempo de espera
Configuración del Entorno
// Nunca hardcodees la configuración — siempre desde el entorno
const config = {
db: {
url: process.env.DATABASE_URL, // requerido
poolSize: parseInt(process.env.DB_POOL_SIZE ?? '10')
},
jwt: {
secret: process.env.JWT_SECRET, // requerido
expiresIn: process.env.JWT_TTL ?? '15m'
}
}
// Valida la configuración requerida al inicio — falla rápido
const required = ['DATABASE_URL', 'JWT_SECRET']
for (const key of required) {
if (!process.env[key]) throw new Error(`Falta la variable de entorno requerida: ${key}`)
}