Express.js
Controllers y handlers HTTP
Explica cómo los handlers traducen input HTTP validado a casos de uso y convierten resultados de aplicación en responses, headers y DTOs públicos.
- Última actualización
- Actualizada
- Nivel
- Aplicación
Express.js
Explica cómo los handlers traducen input HTTP validado a casos de uso y convierten resultados de aplicación en responses, headers y DTOs públicos.
Un handler HTTP traduce entre dos mundos: convierte input de transporte en una llamada de aplicación y convierte el resultado o error en un contrato HTTP.
El handler no debería contener toda la operación. Su responsabilidad principal es:
HTTP input
→ datos validados y contexto
→ caso de uso
→ resultado de aplicación
→ HTTP responseEsto mantiene Express en el boundary y permite que la lógica se pruebe o reutilice sin request/response.
Cuando una ruta crece, suele acumular parsing, queries, reglas, emails y serialización. El resultado es difícil de probar, transaccionar y reutilizar.
Separar handler y caso de uso no significa mover cada línea a un archivo llamado service. Significa aislar responsabilidades reales.
export function makeCreateOrderHandler(createOrder: CreateOrder): RequestHandler {
return async (_request, response) => {
const result = await createOrder.execute({
actor: response.locals.actor,
input: response.locals.validatedBody,
});
response
.status(201)
.location(`/api/orders/${result.id}`)
.json({ data: toOrderResponse(result) });
};
}Flujo:
Un handler puede leer:
Evita casts repetidos:
const input = request.body as CreateOrderInput;Mejor, un middleware de validación produce res.locals.validatedBody, o el handler ejecuta el schema y usa el resultado tipado.
No devuelvas entidades internas directamente. Un mapper controla:
La factory expresa dependencias:
function makeGetOrderHandler(getOrder: GetOrder): RequestHandler {
return async (_request, response) => {
const order = await getOrder.execute({
actor: response.locals.actor,
orderId: response.locals.orderId,
});
response.status(200).json({ data: toOrderResponse(order) });
};
}No necesitas un contenedor DI para beneficiarte de inyección explícita.
“Delgado” no significa que tenga un máximo de líneas. Significa que sus decisiones pertenecen a HTTP:
Una transformación de dinero o regla de stock no pertenece ahí.
El caso de uso puede lanzar errores de aplicación o devolver un Result. El handler no debería repetir un try/catch idéntico en cada ruta.
app.use(errorHandler);Un catch local tiene sentido cuando la operación necesita añadir contexto, compensar o mapear un caso especial.
Incorrecto:
await createOrder.execute(request, response);Esto acopla el negocio a Express y permite que capas internas respondan directamente.
Correcto:
await createOrder.execute({ actor, input });export function makeUpdateOrderHandler(updateOrder: UpdateOrder): RequestHandler {
return async (request, response) => {
const expectedVersion = parseIfMatch(request.get('if-match'));
const order = await updateOrder.execute({
actor: response.locals.actor,
orderId: response.locals.orderId,
expectedVersion,
input: response.locals.validatedBody,
});
response
.status(200)
.set('ETag', `"order-${order.version}"`)
.json({ data: toOrderResponse(order) });
};
}El handler interpreta If-Match; el caso de uso aplica la regla de concurrencia.
Una lista vacía es 200 con []; un recurso individual ausente se expresa como error de aplicación.
No ejecutes el caso de uso después de un middleware terminal. La composición debe impedirlo.
Si el pedido se creó pero serializar la response falla, no puedes “deshacer” automáticamente. Idempotencia y consulta posterior ayudan al cliente.
Un CRUD generator puede ahorrar código, pero suele ocultar autorización, transacciones y estados específicos.
try/catch que convierte cualquier error en 400.req y res al service.Comprueba status, headers, body y middleware.
Comprueba reglas sin Express.
Comprueba representación pública y ausencia de secretos.
No mocks cada detalle del request si una prueba Supertest puede verificar el boundary real.
Handlers separados añaden archivos, pero reducen acoplamiento cuando existe negocio real. Para una ruta de health de tres líneas, una abstracción adicional no aporta valor.
request al caso de uso?try/catch?Lógica de negocio y casos de uso mueve la operación fuera del transporte y define sus reglas, dependencias y garantías.