Saltar al contenido
Curso de Node.js y APIs
Todos los cursos Ir al ejercicio →

Servidor HTTP con Express

  • ⏱ 60 minutos
  • 📶 Nivel: intermedio
  • 🧩 Incluye ejercicio

💡 🎯 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.get y app.post.
  • Leer req.params, req.query y req.body.
  • Responder con res.json y 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.

Clientenavegador, app o curl
→petición
Tu servidorNode + Express
→respuesta
Clienterecibe HTML o JSON

Una 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:

Terminal
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:

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}`);
});
Terminal
node --watch index.js
Salida esperada
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:

  1. express() crea la aplicación (app).
  2. app.get("/", manejador) dice: «cuando llegue una petición GET a la ruta /, ejecuta esta función».
  3. El manejador recibe req (la petición) y res (la respuesta). Con res.send(...) envías el cuerpo.
  4. 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:

index.js
// 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 });
});
Terminal
curl http://localhost:3000/saludo/Ana
curl "http://localhost:3000/suma?a=2&b=3"
Salida esperada
Hola, Ana
{"a":2,"b":3,"resultado":5}
PropiedadContieneEjemplo para GET /suma?a=2&b=3
req.methodEl método HTTP"GET"
req.pathLa ruta sin el query"/suma"
req.paramsSegmentos con :nombre{} aquí; { nombre: "Ana" } en /saludo/:nombre
req.queryDatos tras el ?{ a: "2", b: "3" }
req.bodyEl cuerpo de la petición (necesita express.json())Lo verás enseguida
req.get("cabecera")Una cabecera de la peticiónreq.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étodoQué envíaEjemplo
res.send(texto)Texto o HTMLres.send("<h1>Hola</h1>")
res.json(objeto)JSON, con la cabecera Content-Type: application/jsonres.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ándarres.sendStatus(204)
res.redirect(ruta)Redirige a otra direcciónres.redirect("/")

El código de estado le dice al cliente cómo fue todo. Por defecto es 200. Los más habituales:

CódigoSignificadoCuándo usarlo
200OKTodo salió bien
201CreadoSe creó un recurso nuevo (tras un POST)
204Sin contenidoÉxito sin nada que devolver (borrados)
400Petición incorrectaEl cliente envió datos inválidos
404No encontradoLa ruta o el recurso no existe
500Error del servidorFalló algo en tu código
Ejemplo con estados
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:

index.js
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:

Terminal (macOS, Linux, Git Bash)
curl -X POST http://localhost:3000/eco -H "Content-Type: application/json" -d '{"mensaje":"hola"}'
Salida esperada
{"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.

Un middleware que registra cada petición
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:

▶ Una cadena de middlewares casera
Consola

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

index.js
// 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:

index.js
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

📒 Apuntes para llevar

  • Un servidor HTTP escucha en un puerto y responde a cada petición.
  • Express: const app = express(), rutas con app.get/post(...) y arranque con app.listen.
  • Datos de entrada: req.params (en la ruta), req.query (tras el ?) y req.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 en req.body.
  • Un middleware recibe (req, res, next): responde o llama a next(). El orden importa.
  • El 404 y el manejador de errores (4 parámetros) van al final.

9.Glosario

TérminoSignificado
ServidorPrograma que escucha peticiones y devuelve respuestas.
PuertoNúmero que identifica qué programa de la máquina escucha (por ejemplo, 3000).
localhostNombre de «esta misma computadora».
RutaCombinación de método HTTP y dirección, como GET /saludo/:nombre.
ExpressFramework de Node para crear servidores y APIs de forma sencilla.
MiddlewareFunción que se ejecuta durante el procesamiento de una petición y puede pasar el control con next().
Código de estadoNúmero que resume el resultado de la petición (200, 404…).
Query stringDatos 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?

En /producto/:id, ¿dónde queda el id de /producto/7?

¿Qué hace falta para leer JSON enviado con POST?

¿Qué código de estado corresponde a «recurso no encontrado»?

Un middleware no llama a next() ni responde. ¿Qué pasa?

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.

Ir al ejercicio