Tres horas. Media hora para contar qué le falta a tu servidor y ver cómo lo resuelve Express, y dos horas y media reescribiéndolo pieza a pieza hasta dejar el registro y los errores en un solo sitio.
Hoy · Hoja de ruta
- 1. Aprende: Qué problemas concretos tiene tu servidor, qué pieza de Express resuelve cada uno, qué es un middleware y cómo se centralizan los errores.
- 2. Haz: Documenta los límites de tu código, reescríbelo con Express ruta a ruta y añade registro y tratamiento central de errores.
- 3. Comprueba: Se comporta igual que el de la semana pasada, con menos código, y ninguna ruta tiene ya su propio
try/catch.
Antes de empezar · 5 minutos, sin apuntes
- ¿Cuántas líneas ocupa hoy tu enrutado, y cuál es su nivel máximo de anidamiento?
- ¿Qué tendrías que tocar para añadir una ruta con dos parámetros?
- ¿Cuántos
try/catchhay repartidos por tus rutas?
Se explica
25 minutos · conceptos y demostración
Hoy se instala la única dependencia de la unidad, y se instala después de haber escrito a mano lo que hace. Ese orden no es casual: es lo que convierte un framework en una herramienta en lugar de en una caja negra.
La cuenta de lo que falta
| Lo que necesitas | Lo que tienes | Lo que costaría |
|---|---|---|
| Rutas con parámetros | Partir el texto a mano | Un comparador de patrones |
| Método y ruta a la vez | Condicionales anidados | Una tabla de rutas |
| Cuerpo JSON ya analizado | Leerlo en cada ruta | Un paso previo común |
| Errores en un solo sitio | Un try/catch por ruta |
Un tratamiento central |
| Registro de peticiones | Repetido en cada rama | Un paso previo común |
| Estáticos | Lo has escrito tú | Bastante código |
Ninguna es imposible. Todas juntas son un framework, y ya existe uno pequeño y muy extendido que hace exactamente eso.
Esta sesión es el punto de la unidad
Si hubiéramos empezado por Express, cada una de sus piezas sería magia con nombre. Después de dos semanas escribiendo esto a mano, app.get("/api/productos/:id", ...) no es magia: es la escalera de condicionales que tienes delante, resuelta.
Y el día que Express se quede corto —una cabecera rara, un flujo que no encaja— sabrás bajar un nivel, porque ese nivel lo has escrito tú.
Mira tu servidor: seguro que hay cosas que haces antes de decidir la ruta —registrar la petición, analizar la URL— y cosas que haces después —cerrar, capturar errores—. Esa idea de «una cadena de pasos por los que atraviesa toda petición» tiene nombre, y es la pieza central de la sesión:
- Registro
- Analizar el cuerpo
- Ruta
- Errores
- Respuesta
El primer Express
npm install express
import express from "express";
const app = express();
app.use(express.json()); // analiza el cuerpo JSON
app.use(express.static("publico")); // sirve los estáticos
app.get("/api/productos", async (peticion, respuesta) => {
respuesta.json(await listar());
});
app.get("/api/productos/:id", async (peticion, respuesta) => {
const producto = await obtener(Number(peticion.params.id));
if (!producto) return respuesta.status(404).json({ error: "No encontrado" });
respuesta.json(producto);
});
app.listen(PUERTO, () => console.log(`Escuchando en el puerto ${PUERTO}`));
Compáralo con lo de la semana pasada. Las dos primeras líneas sustituyen tu lector de cuerpo y tu servidor de estáticos enteros; :id sustituye el partido de texto a mano; y respuesta.json pone la cabecera, serializa y cierra.
| A mano | Con Express |
|---|---|
| Condicionales por método y ruta | app.get, app.post, app.put, app.delete |
| Partir la ruta para el identificador | peticion.params.id |
| Analizar la cadena de consulta | peticion.query |
| Acumular y analizar el cuerpo | express.json() |
| Escribir estado, cabeceras y cerrar | respuesta.status(...).json(...) |
| Tu servidor de ficheros | express.static(...) |
Express no valida por ti
Que peticion.body exista no significa que traiga lo que esperas: puede llegar vacío, con campos de más, con tipos que no son. Tus funciones de validación de la UD3 siguen siendo igual de necesarias, y ahora más, porque quien llama a la API puede ser cualquiera.
Un framework quita trabajo repetitivo. No quita responsabilidad.
Express recorre lo declarado en orden hasta que algo responde. De ahí dos consecuencias que causan casi todos los desconciertos del primer día: el analizador de cuerpo va antes que las rutas que lo usan, y el 404 va al final, después de todas.
app.use(express.json()); // antes de las rutas que leen el cuerpo
app.get("/api/productos", ...);
app.use((peticion, respuesta) => respuesta.status(404).json({ error: "No encontrado" }));
Qué es un middleware
Middleware
Una función que recibe la petición, la respuesta y una tercera función, next. Puede mirar o modificar la petición, puede responder y cortar la cadena, o puede llamar a next para que siga el paso siguiente.
function registrar(peticion, respuesta, next) {
const inicio = Date.now();
respuesta.on("finish", () => {
const ms = Date.now() - inicio;
console.log(`${peticion.method} ${peticion.originalUrl} → ${respuesta.statusCode} (${ms} ms)`);
});
next();
}
app.use(registrar);
Lo que hace útil este ejemplo es dónde se registra: al terminar la respuesta, de modo que el estado y el tiempo ya son conocidos. Una sola declaración cubre además todas las rutas, presentes y futuras.
Un middleware que no responde ni llama a next cuelga la petición
La petición se queda dentro de la cadena, sin avanzar y sin respuesta, hasta que el cliente se cansa. No hay error, no hay traza, no hay nada en los registros. Es el fallo más desconcertante de Express, y siempre es el mismo olvido.
Errores en un solo sitio
app.get("/api/productos/:id", async (peticion, respuesta, next) => {
try {
const producto = await obtener(Number(peticion.params.id));
if (!producto) throw new ErrorNoEncontrado("Producto no encontrado");
respuesta.json(producto);
} catch (error) {
next(error); // se lo pasa al manejador de errores
}
});
// El último de todos, y con cuatro parámetros
app.use((error, peticion, respuesta, next) => {
if (error instanceof ErrorDeValidacion) {
return respuesta.status(400).json({ error: error.message, detalles: error.errores });
}
if (error instanceof ErrorNoEncontrado) {
return respuesta.status(404).json({ error: error.message });
}
console.error(error); // la traza, para ti
respuesta.status(500).json({ error: "Error interno del servidor" });
});
Los cuatro parámetros son lo que distingue a un manejador de errores de un middleware normal: si quitas el next final, Express lo tratará como uno cualquiera y no lo llamará nunca.
La traza es para ti; el mensaje, para quien llama
Devolver al cliente el error completo revela rutas de ficheros, versiones y estructura interna, que es precisamente la información que busca quien intenta atacar un sistema. Tampoco aporta nada a quien solo pretendía consultar un producto.
Registra el detalle en el servidor y responde un mensaje genérico con el estado correcto. Esa es la razón de que el manejador tenga la última palabra sobre qué sale.
Se trabaja
150 minutos · del servidor propio al framework
El primer paso documenta lo que tienes antes de sustituirlo; sin él, la reescritura no enseña nada. Los dos últimos miden el cambio y provocan a propósito los tres fallos del primer día con Express.
Paso 1 · El informe · 25 min
Sobre tu propio código, antes de instalar nada:
- Cuenta las líneas del enrutado y el nivel máximo de anidamiento.
- Marca todo lo que se repite en más de una ruta.
- Escribe qué habría que cambiar para añadir
GET /api/categorias/:nombre/productos. - Escribe un comparador de rutas con parámetros y comprueba lo que cuesta. Basta con que funcione para un parámetro.
- Redacta media página: qué problemas tiene tu servidor y qué esperas de un framework.
Guarda ese informe. Al final de la sesión lo vas a contrastar con lo que ha ocurrido de verdad.
Paso 2 · La reescritura · 45 min
Es el trabajo central de la sesión.
- Instala Express y anota qué cambió en
package.jsony en el fichero de bloqueo. - Reescribe las rutas de lectura y comprueba con tu fichero
peticiones.httpde la sesión 4. - Reescribe la creación con validación y 201.
- Sustituye tu servidor de estáticos por
express.static. - Añade el 404 final.
- Mueve las rutas a
src/rutas.jscon un router propio, para queservidor.jssolo monte la aplicación.
Antes de continuar: ejecuta el peticiones.http entero contra la versión nueva. Las respuestas deben ser idénticas a las de la semana pasada, código por código y cuerpo por cuerpo. Si alguna cambia, la reescritura ha modificado el comportamiento y eso no era el encargo.
Paso 3 · La cadena completa · 45 min
- Escribe el middleware de registro con método, ruta, estado y tiempo.
- Escribe uno que rechace cuerpos demasiado grandes.
- Convierte todas tus rutas para que deleguen los errores con
next. - Escribe el manejador de errores central con sus tres casos.
- Comprueba que un fallo interno responde 500 sin filtrar la traza al cliente, y que la traza sí aparece en tu consola.
- Añade un identificador único a cada petición, sácalo en los registros y devuélvelo en la respuesta de error. Así, cuando alguien te diga «me ha dado un error», podrás encontrar su petición exacta.
Paso 4 · Antes y después, en números · 15 min
Recupera el informe del paso 1 y compáralo con el resultado.
| Medida | A mano | Con Express | Qué se ha ido |
|---|---|---|---|
| Líneas del enrutado | |||
| Nivel máximo de anidamiento | |||
Número de try/catch |
|||
| Líneas para leer el cuerpo | |||
| Líneas para servir estáticos | |||
| Líneas para añadir una ruta nueva |
La última fila es la importante: mide el coste del próximo cambio, no el de este. Un framework se paga una vez y se cobra en cada ruta que añadas después.
Responde también a esto: ¿qué parte de lo que esperabas en tu informe no te ha dado Express, y sigue siendo trabajo tuyo?
Paso 5 · Los tres desconciertos del primer día · 20 min
Provócalos a propósito, en este orden, y anota el síntoma exacto de cada uno. Son los tres que te van a pasar, y reconocerlos ahorra tardes enteras.
- Pon
express.json()después de la ruta que lee el cuerpo. ¿Qué valepeticion.body? ¿Da algún error? - Pon el 404 final antes de las rutas. ¿Qué responde
GET /api/productos? - Escribe un middleware que no responda ni llame a
next. ¿Qué ve el cliente? ¿Qué aparece en tus registros?
| Fallo provocado | Síntoma | ¿Hay error en consola? | Cómo lo reconocerás la próxima vez |
|---|---|---|---|
| 1 | |||
| 2 | |||
| 3 |
El tercero es el peor de los tres, precisamente porque la columna del error está vacía.
Añade un cuarto por tu cuenta: quita el next final del manejador de errores, dejándolo con tres parámetros. Comprueba qué pasa cuando una ruta falla.
Ampliación si has completado el trabajo
Primero termina y comprueba los cinco pasos. El primer reto es el que de verdad cierra la unidad: escribir tú lo que acabas de instalar.
Reto 1 · Tu propio mini-framework
Has visto qué hace Express. Escribe una versión mínima tuya, sobre el módulo http nativo, y comprueba cuánto de la magia era magia.
- Implementa
use(fn)que acumule middlewares en una lista. - Implementa el recorrido de esa lista: cada middleware recibe
next, y llamarlo pasa al siguiente. Si ninguno responde, se acaba en un 404. - Implementa
get(patron, fn)ypost(patron, fn)con un comparador que entienda:parametroy deje el valor enpeticion.params. - Implementa un middleware
json()propio que acumule el cuerpo y lo analice, tratando el JSON inválido como un 400. - Reescribe con tu framework dos rutas de tu API y comprueba que responden igual.
- Cuenta las líneas que te ha costado y compáralas con lo que hace Express. Escribe en tres líneas qué has entendido de Express que antes no entendías, y qué no has implementado de lo que él sí hace.
Reto 2 · Que las rutas no repitan el try/catch
Todas tus rutas asíncronas terminan igual: un try con la lógica y un catch que llama a next. Eso es repetición, y la repetición se elimina.
- Escribe una función
envolver(fn)que reciba un manejador asíncrono y devuelva otro que capture cualquier rechazo y lo pase anext. - Aplícala a todas tus rutas y borra los
try/catch. Comprueba que los errores siguen llegando al manejador central. - Provoca un error dentro de una ruta asíncrona sin el envoltorio y anota qué pasa. En versiones antiguas de Express la petición se quedaba colgada; comprueba qué hace la tuya.
- Averigua desde qué versión de Express eso cambió, y qué implica para el código que encuentres por ahí.
- Como segunda parte, escribe un middleware que limite el tamaño del cuerpo y devuelva 413. Decide el límite y justifícalo.
- Explica en tres líneas por qué este envoltorio es, conceptualmente, lo mismo que
aplicarATodosde la UD3: una función que recibe una función y devuelve otra.
Cierre
5 minutos · comprobación y recuerdo
Lista de verificación de la sesión
- Nombras seis problemas concretos de tu servidor a mano.
- Tu servidor está reescrito con Express y responde exactamente igual.
- Sabes qué pieza sustituye a cada cosa que escribiste.
- El registro y los errores están en un solo sitio.
- Sigues validando la entrada: Express no lo hace por ti.
- El cliente nunca ve una traza.
Antes de cerrar · 3 minutos, sin mirar
- ¿Qué hace
express.json()y dónde debe declararse? - ¿Dónde vive el identificador de
/api/productos/:id, y de qué tipo llega? - ¿Por qué el 404 va al final?
- ¿Qué ocurre si un middleware no responde ni llama a
next? - ¿En qué se distingue un manejador de errores de un middleware normal?
- ¿Por qué no se devuelve la traza al cliente?
Ver respuestas
1 · Analiza el cuerpo JSON de la petición, y va antes de las rutas que lo usan.
2 · En peticion.params.id, y llega como texto.
3 · Porque Express recorre en orden: si va antes, responde 404 a todo.
4 · Que la petición se queda colgada sin respuesta ni error.
5 · En que tiene cuatro parámetros, empezando por el error.
6 · Porque revela información interna del sistema y no ayuda a quien llama.
Microprueba semanal 5 · 5–10 minutos
Individual, sin IA y sin apuntes.
- Nombra tres problemas del servidor escrito a mano y qué pieza de Express resuelve cada uno.
- ¿Qué ocurre si un middleware no responde ni llama a
next? - ¿En qué se distingue un manejador de errores de un middleware normal?