Servidor HTTP con Express
💡 🎯 Al terminar esta lección podrás…
- Explicar el ciclo petición-respuesta y qué es un puerto.
- Crear un servidor con Express y definir rutas con
app.getyapp.post. - Leer
req.params,req.queryyreq.body. - Responder con
res.jsony códigos de estado. - Entender y escribir middleware, incluido el 404.
1.Qué hace un servidor HTTP
Cuando abres una página, tu navegador (el cliente) hace una petición a otra computadora (el servidor) y esta devuelve una respuesta. Eso es HTTP. Con Node puedes escribir tú el servidor: un programa que se queda escuchando en un puerto y responde a cada petición.
peticiónrespuestaUna petición lleva un método (GET, POST…), una ruta (/productos) y a veces un cuerpo con datos. La respuesta lleva un código de estado (200, 404…) y su propio cuerpo.
En tu computadora, el servidor es accesible en http://localhost:3000: localhost significa «esta misma máquina» y 3000 es el puerto (un número entre 1 y 65535 que identifica el programa que escucha).
Node incluye el módulo http para esto, pero hacerlo a mano es tedioso: tendrías que analizar las rutas, leer el cuerpo y poner cabeceras tú mismo. Por eso casi todo el mundo usa un framework. El más extendido es Express.
2.Tu primer servidor con Express
Crea una carpeta, ejecuta npm init -y, añade "type": "module" a package.json (lección 2) e instala Express:
npm install express
📌 Nota · Express 5
Desde 2025 la versión que instala npm install express es la 5 (requiere Node 18 o superior). Casi todo lo que verás sirve igual en Express 4, que es el que aparece en muchos tutoriales antiguos; señalamos las diferencias cuando importan.
Ahora crea index.js:
import express from "express";
const app = express();
const PORT = 3000;
app.get("/", (req, res) => {
res.send("¡Hola desde Express!");
});
app.listen(PORT, () => {
console.log(`Servidor en http://localhost:${PORT}`);
});
node --watch index.js
Servidor en http://localhost:3000
Abre http://localhost:3000 en el navegador: verás «¡Hola desde Express!». Detén el servidor con Ctrl + C. Fíjate en las piezas:
express()crea la aplicación (app).app.get("/", manejador)dice: «cuando llegue una petición GET a la ruta/, ejecuta esta función».- El manejador recibe
req(la petición) yres(la respuesta). Conres.send(...)envías el cuerpo. app.listen(PORT, ...)arranca el servidor y lo deja esperando peticiones.
⚠️ Importante · Puerto ocupado
Si ves Error: listen EADDRINUSE: address already in use :::3000, otro programa (quizá tu propio servidor anterior que no cerraste) ya usa el puerto 3000. Ciérralo con Ctrl + C o cambia el número de PORT.
3.Rutas, parámetros y query
Una ruta combina un método HTTP y una dirección. Puedes definir tantas como quieras, y los datos variables llegan por tres sitios:
// 1. Parámetros de ruta: /saludo/Ana
app.get("/saludo/:nombre", (req, res) => {
res.send(`Hola, ${req.params.nombre}`);
});
// 2. Query string: /suma?a=2&b=3
app.get("/suma", (req, res) => {
const a = Number(req.query.a);
const b = Number(req.query.b);
res.json({ a, b, resultado: a + b });
});
curl http://localhost:3000/saludo/Ana
curl "http://localhost:3000/suma?a=2&b=3"
Hola, Ana
{"a":2,"b":3,"resultado":5}
| Propiedad | Contiene | Ejemplo para GET /suma?a=2&b=3 |
|---|---|---|
req.method | El método HTTP | "GET" |
req.path | La ruta sin el query | "/suma" |
req.params | Segmentos con :nombre | {} aquí; { nombre: "Ana" } en /saludo/:nombre |
req.query | Datos tras el ? | { a: "2", b: "3" } |
req.body | El cuerpo de la petición (necesita express.json()) | Lo verás enseguida |
req.get("cabecera") | Una cabecera de la petición | req.get("User-Agent") |
⚠️ Importante · Todo llega como texto
req.params y req.query siempre contienen textos. Si esperas un número, conviértelo con Number(...) y comprueba que no sea NaN.
Los distintos métodos (app.post, app.put, app.delete) se usan igual; los estudiarás a fondo en la lección 5.
4.Responder: send, json y status
El objeto res tiene varios métodos para construir la respuesta. Estos son los que usarás cada día:
| Método | Qué envía | Ejemplo |
|---|---|---|
res.send(texto) | Texto o HTML | res.send("<h1>Hola</h1>") |
res.json(objeto) | JSON, con la cabecera Content-Type: application/json | res.json({ ok: true }) |
res.status(codigo) | Fija el código de estado (se encadena) | res.status(404).json({ error: "No existe" }) |
res.sendStatus(codigo) | Envía solo el código con su texto estándar | res.sendStatus(204) |
res.redirect(ruta) | Redirige a otra dirección | res.redirect("/") |
El código de estado le dice al cliente cómo fue todo. Por defecto es 200. Los más habituales:
| Código | Significado | Cuándo usarlo |
|---|---|---|
200 | OK | Todo salió bien |
201 | Creado | Se creó un recurso nuevo (tras un POST) |
204 | Sin contenido | Éxito sin nada que devolver (borrados) |
400 | Petición incorrecta | El cliente envió datos inválidos |
404 | No encontrado | La ruta o el recurso no existe |
500 | Error del servidor | Falló algo en tu código |
app.get("/producto/:id", (req, res) => {
const id = Number(req.params.id);
if (Number.isNaN(id)) {
return res.status(400).json({ error: "El id debe ser un número" });
}
if (id !== 1) {
return res.status(404).json({ error: "Producto no encontrado" });
}
res.json({ id: 1, nombre: "Cuaderno", precio: 12.5 });
});
💡 Consejo · Usa return al responder pronto
Cada petición admite una sola respuesta. Escribe return res.status(400)... en los casos de error para que la función termine ahí. Si no, el código continúa y se intenta responder dos veces (ERR_HTTP_HEADERS_SENT).
5.Recibir JSON: express.json()
Cuando un cliente envía datos en el cuerpo de la petición (normalmente con POST), Express no los interpreta por sí solo. Necesitas activar el middleware express.json(), que lee el cuerpo y lo deja convertido en un objeto en req.body:
app.use(express.json()); // antes de las rutas que lo necesiten
app.post("/eco", (req, res) => {
const { mensaje } = req.body ?? {};
if (!mensaje) {
return res.status(400).json({ error: "Falta el campo mensaje" });
}
res.status(201).json({ recibido: mensaje });
});
Para probarlo, envía una petición POST con JSON:
curl -X POST http://localhost:3000/eco -H "Content-Type: application/json" -d '{"mensaje":"hola"}'
{"recibido":"hola"}
⚠️ Importante · En Windows PowerShell
curl es un alias de otro comando. Escribe curl.exe y escapa las comillas del JSON: curl.exe -X POST http://localhost:3000/eco -H "Content-Type: application/json" -d "{\"mensaje\":\"hola\"}". Si te resulta incómodo, usa una extensión del editor (también en Cursor) como REST Client o Thunder Client, que permiten probar peticiones con formularios.
📌 Nota · req.body sin datos
En Express 5, si la petición no trae cuerpo JSON, req.body es undefined (en Express 4 era un objeto vacío). Por eso el ejemplo usa req.body ?? {} para no fallar.
6.Middleware: funciones en cadena
Un middleware es una función que se ejecuta entre la llegada de la petición y la respuesta final. Recibe req, res y next. Puede mirar o modificar la petición, y luego tiene dos opciones: llamar a next() para pasar al siguiente, o responder y cortar la cadena.
app.use((req, res, next) => {
console.log(`${req.method} ${req.url}`);
next(); // sin esto, la petición se queda colgada
});
Los middlewares se ejecutan en el orden en que los registras. Por eso express.json() debe ir antes de las rutas que usan req.body. Experimenta con la idea en este editor, que simula la cadena en JavaScript puro:
Observa que en /privado el tercer middleware nunca se ejecuta: el segundo respondió y no llamó a next(). Así funcionan la autenticación, el registro, la lectura del cuerpo y casi todo en Express.
Tres middlewares que casi siempre usarás
// 1. Archivos estáticos: sirve lo que haya en la carpeta public/
app.use(express.static("public"));
// 2. Ruta no encontrada (404): va DESPUÉS de todas tus rutas
app.use((req, res) => {
res.status(404).json({ error: "Ruta no encontrada" });
});
// 3. Manejador de errores: lleva CUATRO parámetros y va al final
app.use((err, req, res, next) => {
console.error(err);
res.status(500).json({ error: "Error interno del servidor" });
});
📌 Nota · El manejador de errores
Express reconoce un manejador de errores por tener cuatro parámetros (err, req, res, next). En Express 5, si una función async lanza un error o rechaza una promesa, llega automáticamente a este manejador; en Express 4 había que llamar a next(err) a mano. Lo estudiarás a fondo en la lección 7.
7.El servidor completo
Juntando todo, este es un servidor pequeño pero completo. Pruébalo, rómpelo y míralo funcionar:
import express from "express";
const app = express();
const PORT = 3000;
app.use(express.json());
app.use((req, res, next) => {
console.log(`${req.method} ${req.url}`);
next();
});
app.get("/", (req, res) => {
res.send("API en marcha");
});
app.get("/saludo/:nombre", (req, res) => {
res.json({ mensaje: `Hola, ${req.params.nombre}` });
});
app.get("/suma", (req, res) => {
const a = Number(req.query.a);
const b = Number(req.query.b);
if (Number.isNaN(a) || Number.isNaN(b)) {
return res.status(400).json({ error: "a y b deben ser números" });
}
res.json({ resultado: a + b });
});
app.post("/eco", (req, res) => {
const { mensaje } = req.body ?? {};
if (!mensaje) {
return res.status(400).json({ error: "Falta el campo mensaje" });
}
res.status(201).json({ recibido: mensaje });
});
app.use((req, res) => {
res.status(404).json({ error: "Ruta no encontrada" });
});
app.listen(PORT, () => {
console.log(`Servidor en http://localhost:${PORT}`);
});
Para saber más sobre todo lo que ofrece, consulta la documentación oficial en expressjs.com.
8.Errores frecuentes
EADDRINUSE: el puerto ya está ocupado por otro programa o por tu servidor anterior.- Olvidar
next()en un middleware: la petición se queda colgada y el navegador gira sin respuesta. - Poner
express.json()después de las rutas:req.bodyllega vacío porque el orden importa. - Responder dos veces (
Cannot set headers after they are sent): falta unreturntras responder en un caso de error. - Olvidar reiniciar el servidor tras editar el código. Usa
node --watch. - Tratar
req.queryyreq.paramscomo números: siempre llegan como texto. - Poner el 404 antes de las rutas: capturaría todas las peticiones. Va al final.
📒 Apuntes para llevar
- Un servidor HTTP escucha en un puerto y responde a cada petición.
- Express:
const app = express(), rutas conapp.get/post(...)y arranque conapp.listen. - Datos de entrada:
req.params(en la ruta),req.query(tras el?) yreq.body(cuerpo). res.json()responde con JSON;res.status(n)fija el código.- Códigos clave: 200 OK, 201 creado, 400 petición incorrecta, 404 no encontrado, 500 error del servidor.
app.use(express.json())es necesario para leer JSON enreq.body.- Un middleware recibe
(req, res, next): responde o llama anext(). El orden importa. - El 404 y el manejador de errores (4 parámetros) van al final.
9.Glosario
| Término | Significado |
|---|---|
| Servidor | Programa que escucha peticiones y devuelve respuestas. |
| Puerto | Número que identifica qué programa de la máquina escucha (por ejemplo, 3000). |
localhost | Nombre de «esta misma computadora». |
| Ruta | Combinación de método HTTP y dirección, como GET /saludo/:nombre. |
| Express | Framework de Node para crear servidores y APIs de forma sencilla. |
| Middleware | Función que se ejecuta durante el procesamiento de una petición y puede pasar el control con next(). |
| Código de estado | Número que resume el resultado de la petición (200, 404…). |
| Query string | Datos que van tras el ? en la dirección. |
| Cuerpo (body) | Datos enviados dentro de la petición, normalmente en JSON. |
🧠 Comprueba lo aprendido
Elige una respuesta en cada pregunta. Verás al instante si acertaste y por qué.
¿Qué recibe siempre un manejador de ruta de Express?
Recibe la petición (req) y la respuesta (res), y opcionalmente next.
En /producto/:id, ¿dónde queda el id de /producto/7?
Los segmentos con : se leen de req.params, como texto ("7").
¿Qué hace falta para leer JSON enviado con POST?
Express no interpreta el cuerpo por defecto: el middleware express.json() lo hace.
¿Qué código de estado corresponde a «recurso no encontrado»?
404 significa que la ruta o el recurso pedido no existe.
Un middleware no llama a next() ni responde. ¿Qué pasa?
Si no responde ni pasa el control, el cliente espera hasta agotar el tiempo.
Tu progreso se guarda en este navegador y se refleja en el índice del curso.
💡 🚀 ¡Ahora te toca!
La práctica fija lo aprendido. Pasa al ejercicio de esta lección.