Con el módulo http de Node.js se puede construir un servidor, pero en cuanto tenés más de
tres rutas el código se vuelve una cadena de if sobre req.url. Express resuelve
eso con un sistema de rutas y middleware simple y flexible, y por eso es el framework más usado del
ecosistema. En el libro Node.js desde cero es el centro de la segunda parte; acá
tenés tu primer servidor, paso a paso.
Instalar Express y el primer servidor
$ mkdir api-blog && cd api-blog
$ npm init -y
$ npm pkg set type=module # usar import/export
$ npm install express
import express from 'express';
const app = express();
const PORT = process.env.PORT ?? 3000;
app.get('/', (req, res) => {
res.send('¡Hola desde Express!');
});
app.listen(PORT, () => console.log(`Servidor en http://localhost:${PORT}`));
Ejecutalo con node --watch src/server.js: con --watch, Node.js reinicia el
servidor cada vez que guardás un cambio.
Rutas y métodos HTTP
Una ruta es la combinación de un método HTTP y una ruta. En una API, el método indica la acción y la ruta, el recurso:
| Método y ruta | Acción | Código de éxito |
|---|---|---|
GET /posts | Listar posts | 200 |
GET /posts/:id | Ver un post | 200 (o 404) |
POST /posts | Crear un post | 201 |
PUT /posts/:id | Reemplazar un post | 200 |
DELETE /posts/:id | Borrar un post | 204 |
Parámetros de ruta y query strings
- Parámetros de ruta (
req.params): identifican el recurso. En/posts/42, con la ruta/posts/:id,req.params.idvale'42'. - Query string (
req.query): opciones sobre el resultado, como filtros, orden o página. En/posts?publicado=true&page=2,req.query.pagevale'2'.
import express from 'express';
const app = express();
app.use(express.json()); // lee cuerpos JSON y los deja en req.body
// Datos en memoria para el ejemplo (en el libro se conectan a una base de datos)
const posts = [
{ id: 1, titulo: 'Hola Node.js', publicado: true },
{ id: 2, titulo: 'El event loop', publicado: true },
{ id: 3, titulo: 'Borrador sobre streams', publicado: false },
];
// GET /posts?publicado=true&page=1&limit=10
app.get('/posts', (req, res) => {
const { publicado, page = '1', limit = '10' } = req.query;
let resultado = posts;
if (publicado !== undefined) {
resultado = resultado.filter((p) => String(p.publicado) === publicado);
}
const pagina = Math.max(1, Number.parseInt(page, 10) || 1);
const limite = Math.min(50, Number.parseInt(limit, 10) || 10); // tope para no devolver todo
const datos = resultado.slice((pagina - 1) * limite, pagina * limite);
res.json({ pagina, total: resultado.length, datos });
});
// GET /posts/2
app.get('/posts/:id', (req, res) => {
const post = posts.find((p) => p.id === Number(req.params.id));
if (!post) {
return res.status(404).json({ error: 'Post no encontrado' });
}
res.json(post);
});
app.listen(3000);
req.params y req.query es texto: por eso
se convierte con Number() antes de comparar con un id numérico. Y como lo controla el
cliente, hay que validarlo (lo vemos en validación y manejo de
errores).
Leer el cuerpo de la petición
// POST /posts con cuerpo { "titulo": "Middleware en Express" }
app.post('/posts', (req, res) => {
const { titulo } = req.body ?? {};
if (typeof titulo !== 'string' || titulo.trim() === '') {
return res.status(400).json({ error: 'El título es obligatorio' });
}
const post = { id: posts.length + 1, titulo: titulo.trim(), publicado: false };
posts.push(post);
res.status(201).location(`/posts/${post.id}`).json(post);
});
Sin app.use(express.json()), req.body llega vacío. Para formularios HTML
clásicos se usa express.urlencoded(). Podés probar la ruta desde la terminal:
$ curl -X POST http://localhost:3000/posts \
-H "Content-Type: application/json" \
-d '{"titulo": "Middleware en Express"}'
Organizar las rutas con Router
Cuando la API crece, conviene separar las rutas de cada recurso en su propio archivo:
import { Router } from 'express';
const router = Router();
router.get('/', (req, res) => { /* listar */ res.json([]); });
router.get('/:id', (req, res) => { /* ver uno */ res.json({ id: req.params.id }); });
router.post('/', (req, res) => { /* crear */ res.status(201).json(req.body); });
export default router;
import express from 'express';
import postsRouter from './routes/posts.js';
const app = express();
app.use(express.json());
app.use('/api/posts', postsRouter); // todas sus rutas cuelgan de /api/posts
export default app;
Separar app.js (la configuración) de server.js (el listen) tiene
una ventaja extra: podés importar la app en los tests sin arrancar el servidor.
Errores comunes
- Responder dos veces: si no hacés
returndespués deres.status(404).json(...), el código sigue y Express lanza “Cannot set headers after they are sent”. - El orden de las rutas:
/posts/:iddefinida antes que/posts/recientescaptura “recientes” como si fuera un id. Definí primero las rutas fijas. - Devolver 200 para todo: el código de estado es parte de la respuesta; un cliente no debería tener que leer el cuerpo para saber si algo falló.
- Seguir tutoriales de Express 4 al pie de la letra: Express 5 cambió algunas cosas, como la sintaxis de los comodines en las rutas y el manejo de errores asíncronos.
De Express básico a una API completa
El libro Node.js desde cero introduce Express.js y sus rutas, y lo lleva hasta una API REST completa para un blog en el primer proyecto.
Comprar en Amazon