Saltar al contenido
express.js · devschool

Servidores web con Express

Lección 37 de 38 · 7 min de lectura · Actualizado el

En esta lección
  1. Instalar
  2. El servidor mínimo
  3. Rutas
  4. Recibir datos: POST y PUT
  5. Servir archivos estáticos
  6. Errores: 404 y 500
  7. Códigos de estado
  8. El programa completo

Hasta ahora todo tu código se ejecutaba en el navegador. Express te permite escribir la otra mitad de una aplicación web, el servidor, con el mismo lenguaje: JavaScript.

Express es un framework para Node.js que facilita crear servidores web y APIs. Es libre, gratuito y uno de los más usados del mundo. Cumple el mismo papel que Django en Python, Laravel en PHP o Spring en Java.

Instalar

En una carpeta nueva para tu proyecto:

npm init -y
npm install express

Y en el package.json, añade "type": "module" para poder usar import:

{
  "name": "mi-servidor",
  "type": "module",
  "dependencies": {
    "express": "^5.1.0"
  }
}

El servidor mínimo

Crea servidor.js:

import express from "express";

const app = express();
const puerto = process.env.PORT || 3000;

app.get("/", (req, res) => {
  res.send("¡Hola desde Express!");
});

app.listen(puerto, () => {
  console.log(`Servidor en http://localhost:${puerto} (Ctrl + C para parar)`);
});

Ejecútalo con node servidor.js y abre http://localhost:3000 en el navegador.

  • express() crea la aplicación.
  • app.get(ruta, manejador) dice qué hacer cuando llega una petición GET a esa ruta.
  • El manejador recibe dos objetos: req (request, la petición: qué ha pedido el cliente) y res (response, la respuesta que vas a enviar).
  • app.listen(puerto, ...) arranca el servidor. process.env.PORT lee el puerto de una variable de entorno (lo que usan los servicios de hosting); si no existe, usa el 3000.

Cada vez que cambies el código tienes que parar el servidor (Ctrl + C) y volver a arrancarlo. Para que se reinicie solo: node --watch servidor.js.

Si ves un error como EADDRINUSE, el puerto ya está ocupado: seguramente tienes otro servidor abierto en otra terminal.

Rutas

Cada combinación de método HTTP y ruta tiene su manejador:

app.get("/about", (req, res) => {
  res.type("text/plain; charset=utf-8");
  res.send("Esto es una prueba de Express");
});

app.get("/api/cursos", (req, res) => {
  res.json([
    { id: "html", nombre: "HTML" },
    { id: "css", nombre: "CSS" },
  ]);
});
  • res.send(texto) responde con texto o HTML. Con res.type(...) indicas de qué tipo es y su codificación.
  • res.json(objeto) responde con JSON: convierte el objeto con JSON.stringify y pone la cabecera adecuada. No lo conviertas tú antes, o enviarás un texto entrecomillado dentro de otro.

Parámetros en la ruta

Los segmentos que empiezan por : capturan lo que haya en esa posición de la URL:

app.get("/api/cursos/:id", (req, res) => {
  const id = req.params.id;
  res.json({ mensaje: `Me has pedido el curso ${id}` });
});

app.get("/api/coordenadas/:x/:y", (req, res) => {
  const { x, y } = req.params;
  res.json({ x: Number(x), y: Number(y) });
});

Una petición a /api/coordenadas/150/300 da x = "150" e y = "300". Los parámetros siempre llegan como texto.

Parámetros de consulta (query)

Lo que va después de ? en la URL está en req.query:

// GET /api/buscar?q=flexbox&pagina=2
app.get("/api/buscar", (req, res) => {
  const texto = req.query.q;          // "flexbox"
  const pagina = Number(req.query.pagina) || 1;
  res.json({ texto, pagina });
});

Regla general: los parámetros de ruta identifican un recurso (/cursos/html); los de consulta filtran, ordenan o paginan (?pagina=2&orden=nombre).

Recibir datos: POST y PUT

Para leer el JSON que envía el cliente en el cuerpo de la petición, activa el middleware express.json():

app.use(express.json());

app.post("/api/tareas", (req, res) => {
  const tarea = req.body;              // el objeto enviado por el cliente
  console.log("Nueva tarea:", tarea);
  res.status(201).json({ id: 7, ...tarea });
});

Un middleware es una función que se ejecuta con todas las peticiones antes de llegar a las rutas. express.json() lee el cuerpo y lo convierte en objeto. (En tutoriales antiguos verás la librería body-parser para esto; desde 2017 no hace falta.)

Las peticiones GET puedes probarlas en el navegador, pero para POST, PUT y DELETE necesitas una herramienta: Postman, Bruno, la extensión Thunder Client de VS Code, o la terminal con curl:

curl -X POST http://localhost:3000/api/tareas \
  -H "Content-Type: application/json" \
  -d '{"titulo": "Estudiar Express"}'

Servir archivos estáticos

Un servidor también debe poder enviar archivos tal cual: tu HTML, CSS, JavaScript e imágenes. Con express.static sirves una carpeta entera:

import path from "node:path";

const carpetaPublica = path.join(import.meta.dirname, "public");
app.use(express.static(carpetaPublica));

Si en public/ tienes index.html, css/estilos.css y js/app.js, el navegador podrá pedirlos como http://localhost:3000/, /css/estilos.css y /js/app.js.

Fíjate: el nombre de la carpeta (public) no aparece en la URL. Esa carpeta es la raíz del sitio web, lo que el navegador ve como /. No la confundas con la raíz del disco duro del servidor, que nunca debe ser accesible desde la web.

Dos buenas prácticas con las rutas en el servidor:

  • Construye las rutas con path.join, que pone las barras correctas en cualquier sistema operativo.
  • Calcula la carpeta a partir de dónde está tu archivo (import.meta.dirname, disponible desde Node.js 20.11) o de una variable de entorno, nunca escribas tu ruta personal (/home/ana/proyecto/public): en otro ordenador no existe. (Verás muchos tutoriales con express.static("public"): funciona, pero depende de desde qué carpeta arranques el servidor, y confunde bastante.)

Errores: 404 y 500

Después de todas las rutas, añade los manejadores de error. El orden importa: Express prueba las rutas de arriba abajo, y estos solo deben ejecutarse si ninguna ha respondido.

// 404: ninguna ruta coincide
app.use((req, res) => {
  res.status(404).type("text/plain").send("404 - No encontrado");
});

// 500: algo ha lanzado un error (tiene 4 parámetros)
app.use((err, req, res, next) => {
  console.error(err);
  res.status(500).type("text/plain").send("500 - Error en el servidor");
});

Códigos de estado

Cada respuesta lleva un código de estado que le dice al cliente cómo ha ido. Los más habituales:

CódigoSignificadoCuándo usarlo
200 OKTodo bienRespuesta normal (es el valor por defecto)
201 CreatedCreadoTras crear algo con POST
204 No ContentBien, sin contenidoTras borrar algo
400 Bad RequestPetición incorrectaFaltan datos o tienen mal formato
401 UnauthorizedNo identificadoHay que iniciar sesión
403 ForbiddenProhibidoIdentificado, pero sin permiso
404 Not FoundNo existeEl recurso pedido no existe
500 Internal Server ErrorError del servidorUn fallo inesperado en tu código
  • Los 4xx son “culpa” del cliente; para el servidor no son un error, sino una situación normal que hay que gestionar.
  • Los 5xx son fallos del servidor: hay que investigarlos.
  • Una lista vacía no es un error: si alguien pide los pedidos de un cliente que no tiene ninguno, la respuesta correcta es 200 con [], no un 404.

El programa completo

import express from "express";
import path from "node:path";

const app = express();
const puerto = process.env.PORT || 3000;

app.use(express.json());
app.use(express.static(path.join(import.meta.dirname, "public")));

app.get("/api/saludo/:nombre", (req, res) => {
  res.json({ mensaje: `Hola, ${req.params.nombre}` });
});

app.post("/api/eco", (req, res) => {
  res.json({ recibido: req.body });
});

app.use((req, res) => res.status(404).send("404 - No encontrado"));
app.use((err, req, res, next) => {
  console.error(err);
  res.status(500).send("500 - Error en el servidor");
});

app.listen(puerto, () => console.log(`Servidor en http://localhost:${puerto}`));

En la siguiente lección usarás todo esto para crear una API REST completa y conectarla con una página.

Pon a prueba lo que has aprendido

[JavaScript] En Express, ¿dónde están los valores de una ruta como /api/cursos/:id?

[JavaScript] ¿Por qué el manejador del 404 debe ir después de todas las rutas?

¿Te ha quedado claro? Márcala y verás tu progreso en el explorador.