Lección 2 de 313 min
Express: servidores HTTP con Node
Rutas, middleware, routers y manejo de errores en el framework web más usado de Node.
Con el módulo node:http puedes crear un servidor, pero enseguida te toca hacerlo todo a mano: separar la ruta de los parámetros, leer el cuerpo de la petición trozo a trozo, decidir qué función atiende cada URL, poner las cabeceras… Para una API de recetas con veinte rutas, eso se convierte en un montón de if difíciles de mantener.
Express resuelve exactamente eso con dos ideas: rutas (qué función atiende cada método y URL) y middleware (funciones que procesan la petición en cadena, una detrás de otra). Es el framework web más usado de Node y la base de muchos otros, así que entenderlo te sirve aunque acabes usando otro.
Tu primer servidor
express(), rutas y listen
Crear la app, asociar cada método y URL a una función y ponerla a escuchar.
- Desde:
- 2010
- Mantiene:
- OpenJS Foundation
express() crea la aplicación. Después registras rutas con un método por cada verbo HTTP: app.get, app.post, app.put, app.delete… Cada ruta recibe la URL y la función que la atiende, el handler (manejador). Por último, app.listen(3000) abre el puerto y empieza a aceptar peticiones.
Las rutas pueden tener parámetros: en /recetas/:id, la parte :id captura lo que venga en esa posición, así que /recetas/7 y /recetas/42 llegan a la misma función con un id distinto. Express prueba las rutas en el orden en que las registras y usa la primera que coincide.
Úsalo cuando
Para APIs REST, backends de aplicaciones web y prototipos: es mínimo, lo entiende todo el mundo y tiene middleware para casi cualquier cosa.
Ten en cuenta
Express es deliberadamente minimalista: no trae validación, base de datos ni estructura de carpetas. Si quieres todo eso de serie, mira NestJS (que funciona sobre Express) o Fastify, que suele ser más rápido y valida con esquemas.
import express from "express";
const app = express();
app.get("/", (req, res) => {
res.send("API de recetas");
});
// :id es un parámetro de ruta
app.get("/recetas/:id", (req, res) => {
res.json({ id: req.params.id, nombre: "Gazpacho" });
});
app.listen(3000, () => {
console.log("Escuchando en http://localhost:3000");
});req y res
Todo lo que llega en la petición y las herramientas para contestar.
- req:
- params, query, body
- res:
- status, json, send
El objeto req (request, «petición») trae lo que envía el cliente: req.params con los parámetros de ruta, req.query con lo que va tras el ? de la URL, req.get("Authorization") con una cabecera y req.body con el cuerpo. Ojo: req.body solo existe si antes un middleware lo ha leído, por ejemplo express.json() para cuerpos JSON.
El objeto res (response, «respuesta») sirve para contestar: res.status(201) fija el código de estado, res.json(datos) envía JSON con su cabecera correcta y res.send() envía texto o HTML. Cada petición debe recibir exactamente una respuesta.
Úsalo cuando
En cada handler. Devuelve siempre el código de estado que corresponde: 201 al crear, 400 si los datos son incorrectos, 404 si no existe el recurso.
Ten en cuenta
Todo lo que llega en params y query es texto: req.query.pagina es "2", no 2. Y responder dos veces (por ejemplo, olvidar un return tras un res.status(400)) provoca el error «Cannot set headers after they are sent».
app.use(express.json()); // lee los cuerpos JSON y los deja en req.body
// GET /recetas?vegana=true&pagina=2
app.get("/recetas", (req, res) => {
const { vegana, pagina = "1" } = req.query; // siempre texto
res.json({ vegana: vegana === "true", pagina: Number(pagina) });
});
app.post("/recetas", (req, res) => {
const { nombre, minutos } = req.body;
if (!nombre) {
return res.status(400).json({ error: "Falta el nombre" });
}
res.status(201).json({ id: 42, nombre, minutos });
});Middleware: la petición en cadena
Imagina una cadena de montaje: la petición entra por un extremo y cada puesto hace su trabajo (anotarla, leer el JSON, comprobar el token) antes de pasarla al siguiente. Cada puesto es un middleware, y el orden en que los colocas decide lo que ocurre.
Middleware y next()
Funciones que reciben la petición, hacen algo y deciden si pasarla al siguiente.
- Firma:
- (req, res, next)
Un middleware es una función con tres parámetros: (req, res, next). Puede leer o modificar req y res y, al terminar, tiene dos opciones: responder (y la cadena se detiene ahí) o llamar a next() para pasar el control al siguiente. Con app.use(fn) se aplica a todas las peticiones; también puedes ponerlo solo en una ruta: app.post("/recetas", exigirToken, crear).
Express ejecuta los middleware en el orden en que los registras. Por eso express.json() va antes que las rutas que leen req.body, y la autenticación va antes que las rutas que protege. Los handlers de las rutas son, en realidad, middleware que responden.
Úsalo cuando
Para todo lo que se repite en muchas rutas: registrar peticiones, leer el cuerpo, comprobar la sesión, medir tiempos o añadir cabeceras.
Ten en cuenta
Si un middleware ni responde ni llama a next(), la petición se queda colgada: el cliente espera hasta que se rinde, sin ningún error en la consola. Y si la autenticación está después de una ruta, esa ruta queda sin proteger.
function registrar(req, res, next) {
const inicio = performance.now();
res.on("finish", () => {
const ms = Math.round(performance.now() - inicio);
console.log(`${req.method} ${req.url} → ${res.statusCode} (${ms} ms)`);
});
next(); // sin esto, la petición se queda colgada
}
function exigirToken(req, res, next) {
if (req.get("Authorization") !== `Bearer ${process.env.TOKEN}`) {
return res.status(401).json({ error: "No autorizado" }); // corta la cadena
}
next();
}
app.use(registrar); // para todas las peticiones
app.post("/recetas", exigirToken, crearReceta); // solo para esta rutaexpress.Router
Agrupar las rutas de un recurso en su propio archivo y montarlas bajo un prefijo.
- Se monta con:
- app.use(prefijo, router)
Cuando la API crece, tener todas las rutas en app.js se vuelve inmanejable. express.Router() crea una especie de «miniaplicación» con sus propias rutas y middleware, que luego montas con app.use("/api/recetas", recetas). Dentro del router, las rutas son relativas: /:id se convierte en /api/recetas/:id.
Lo habitual es un router por recurso (recetas.js, usuarios.js) y aplicar a cada uno solo los middleware que necesita: por ejemplo, autenticación en el router de administración y no en el público.
Úsalo cuando
En cuanto tengas más de un recurso o más de un puñado de rutas. También para versionar una API: app.use("/api/v1", v1) y app.use("/api/v2", v2).
Ten en cuenta
Para leer parámetros del prefijo (como :autorId en /autores/:autorId/recetas) desde el router, créalo con express.Router({ mergeParams: true }); si no, req.params.autorId llega vacío.
// rutas/recetas.js
import express from "express";
export const recetas = express.Router();
recetas.get("/", listarRecetas); // GET /api/recetas
recetas.get("/:id", verReceta); // GET /api/recetas/7
recetas.post("/", exigirToken, crearReceta); // POST /api/recetas
// app.js
import { recetas } from "./rutas/recetas.js";
app.use("/api/recetas", recetas);Manejo de errores
Un middleware de cuatro parámetros, al final de todo, que recoge cualquier fallo.
- Firma:
- (err, req, res, next)
Cuando algo falla, llamas a next(error). Express se salta entonces todos los middleware normales y busca el siguiente middleware de errores, que se distingue por tener cuatro parámetros: (err, req, res, next). Se registra al final, después de todas las rutas, y es el lugar para registrar el error y responder con un mensaje genérico.
En Express 5, si un handler async lanza un error o su promesa es rechazada, Express llama a next(error) por ti. En Express 4 tenías que envolver cada handler en un try/catch o usar un paquete auxiliar. Justo antes del manejador de errores suele ir un middleware que responde 404 a lo que no coincidió con ninguna ruta.
Úsalo cuando
En toda aplicación: un manejador de errores central evita repetir try/catch en cada ruta y garantiza que ningún error deja la petición sin respuesta.
Ten en cuenta
Tiene que declarar los cuatro parámetros aunque no uses next: con tres, Express lo trata como un middleware normal. Y no envíes err.stack ni mensajes internos al cliente en producción: dan pistas a un atacante.
app.get("/recetas/:id", async (req, res) => {
// Si la consulta falla, Express 5 llama a next(error) automáticamente
const receta = await db.recetas.buscar(req.params.id);
if (!receta) return res.status(404).json({ error: "No existe" });
res.json(receta);
});
// 404: ninguna ruta coincidió
app.use((req, res) => {
res.status(404).json({ error: "Ruta no encontrada" });
});
// Manejador de errores: cuatro parámetros, siempre al final
app.use((err, req, res, next) => {
console.error(err);
res.status(err.status ?? 500).json({ error: "Algo salió mal" });
});Middleware habituales: cors y helmet
Paquetes que resuelven en una línea problemas que tiene cualquier API.
- cors:
- Otros orígenes
- helmet:
- Cabeceras de seguridad
cors añade las cabeceras que permiten que una web de otro origen (otro dominio o puerto) llame a tu API desde el navegador. Sin ellas, el navegador bloquea la respuesta. helmet añade de una vez varias cabeceras de seguridad, como Content-Security-Policy o X-Content-Type-Options, que protegen frente a ataques habituales.
Express también trae algunos integrados además de express.json(): express.urlencoded() lee formularios HTML y express.static("public") sirve archivos estáticos como imágenes o CSS.
Úsalo cuando
helmet en prácticamente cualquier servidor que sirva páginas o una API pública; cors cuando tu frontend vive en un origen distinto al de la API.
Ten en cuenta
cors() sin opciones permite cualquier origen. Indica explícitamente cuáles aceptas. Verás en detalle por qué existen CORS y estas cabeceras en el módulo de seguridad web.
import express from "express";
import cors from "cors";
import helmet from "helmet";
const app = express();
app.use(helmet()); // cabeceras de seguridad
app.use(cors({ origin: "https://mitienda.com" })); // quién puede llamarte
app.use(express.json()); // cuerpos JSON
app.use(express.static("public")); // archivos estáticosPruébalo
En el simulador, pulsa «Auth después de las rutas» y envía un POST /recetas sin token: la receta se crea igualmente. Después quita express.json() o haz que el logger olvide llamar a next(), y observa dónde se detiene la petición.
Interactivo
El recorrido de una petición por Express
Activa, desactiva y reordena los middleware, elige una petición y pulsa «Enviar» para ver dónde se detiene.
Petición
- 1
app.use(registrar)next()Anota «POST /recetas» y llama a next().
- 2
app.use(express.json())next()Convierte el cuerpo en req.body y llama a next().
- 3
app.use(exigirToken)next()El token es válido: llama a next().
- 4
app.get/post("/recetas", …)respondePOST /recetas coincide: crea la receta y responde 201.
- 5
app.use((req, res) => …)no llegaNo se ejecuta: la respuesta ya se envió (o nunca se enviará).
- 6
app.use((err, req, res, next) => …)no llegaNo se ejecuta: la respuesta ya se envió (o nunca se enviará).
HTTP 201
{ "id": 3, "nombre": "Tortilla" }Todo en orden: JSON leído, token válido y receta creada.
Ponlo en práctica
Saber qué es cada cosa es la mitad del trabajo. La otra mitad es reconocer cuándo usarla.
Interactivo
¿Qué usarías?
Lee cada situación y elige la opción que mejor la resuelve. Fallar aquí no cuenta: es para practicar.
Tu POST /recetas falla con «Cannot read properties of undefined (reading 'nombre')» aunque el cliente envía un JSON correcto.
Para recordar
- Express organiza un servidor en rutas (método + URL → handler) y middleware que procesan la petición en cadena.
- Cada middleware responde o llama a
next(); si no hace ninguna de las dos cosas, la petición se queda colgada. - El orden importa:
express.json()y la autenticación van antes de las rutas, el 404 y el manejador de errores, al final. - El manejador de errores tiene cuatro parámetros, y en Express 5 recibe también los errores de los handlers
async. - Todo lo que llega en
req.paramsyreq.queryes texto, y cada petición debe recibir una sola respuesta.
Comprueba lo aprendido
Cuestionario
Responde las 5 preguntas para completar la lección.
1.Un middleware de autenticación detecta que falta el token. ¿Qué debe hacer?
2.Registras
app.post("/recetas", crear)y, en la línea siguiente,app.use(exigirToken). ¿Qué pasa con unPOST /recetassin token?3.¿Cómo distingue Express un middleware de errores de uno normal?
4.En Express 5, un handler
asynchaceawait db.buscar()y la base de datos falla. ¿Qué ocurre?5.Tras validar los datos haces
res.status(400).json(...), pero sinreturn, y el handler continúa conres.status(201).json(...). ¿Qué pasa?