Sistema de Rutas y Controladores en Express
En esta lección aprenderás a dominar el sistema de rutas de Express y cómo estructurar controladores para crear APIs RESTful escalables y mantenibles. Este es el corazón de cualquier aplicación Express, y dominarlo te permitirá construir servicios web robustos y bien organizados.
En Express, las rutas definen los endpoints de tu API y mapean las solicitudes HTTP entrantes a funciones específicas llamadas controladores. Esta separación entre la definición de rutas y la lógica de negocio es fundamental para mantener un código limpio y testeable.
Fundamentos del Sistema de Rutas
Una ruta en Express está compuesta por tres elementos principales: el método HTTP, la path (ruta) y un handler (manejador). Cuando una solicitud HTTP coincide con una ruta definida, Express ejecuta la función manejadora correspondiente.
app.METHOD(PATH, HANDLER)
// Ejemplos básicos:
app.get('/usuarios', obtenerUsuarios)
app.post('/usuarios', crearUsuario)
app.put('/usuarios/:id', actualizarUsuario)
app.delete('/usuarios/:id', eliminarUsuario)
Métodos HTTP Soportados
Express soporta todos los métodos HTTP estándar. Para una API RESTful, los más utilizados son:
| Método | Verbo HTTP | Uso Típico | Ejemplo |
|---|---|---|---|
| GET | Obtener | Recuperar recursos | GET /productos |
| POST | Crear | Crear nuevos recursos | POST /productos |
| PUT | Reemplazar | Actualización completa | PUT /productos/123 |
| PATCH | Modificar | Actualización parcial | PATCH /productos/123 |
| DELETE | Eliminar | Eliminar recursos | DELETE /productos/123 |
app.router para acceder al enrutador de Express y verificar el orden de tus rutas durante el desarrollo. Esto te ayudará a detectar conflictos de enrutamiento antes de que se conviertan en problemas en producción.
Parámetros de Ruta y Query Strings
Express permite definir dos tipos de parámetros en las rutas: parámetros de ruta (route parameters) y parámetros de consulta (query parameters).
Parámetros de Ruta
Los parámetros de ruta se definen con dos puntos (:) y capturan valores dinámicos del URL:
// Ruta con parámetro dinámico :id
app.get('/usuarios/:id', (req, res) => {
const usuarioId = req.params.id;
// usuarioId contendrá el valor real de la URL
res.json({ id: usuarioId });
});
// Múltiples parámetros
app.get('/usuarios/:usuarioId/posts/:postId', (req, res) => {
const { usuarioId, postId } = req.params;
res.json({ usuario: usuarioId, post: postId });
});
// Parámetros con modificadores opcionales
app.get('/archivos(*path)?', (req, res) => {
// Captura segmentos de ruta opcionales
});
Query Strings
Los query strings son parámetros que aparecen después del símbolo ? en la URL:
// GET /productos?categoria=electronica&orden=precio
app.get('/productos', (req, res) => {
const { categoria, orden, pagina = 1 } = req.query;
console.log('Categoría:', categoria); // 'electronica'
console.log('Orden:', orden); // 'precio'
console.log('Página:', pagina); // '1'
// Lógica de filtrado y paginación
res.json({ categoria, orden, pagina });
});
req.query siempre son strings. Si necesitas números o booleanos, debes convertirirlos explícitamente. Además,valida siempre los inputs del usuario para prevenir inyecciones y ataques maliciosos.
Introducción a los Controladores
Los controladores son funciones que contienen la lógica de negocio de tu aplicación. Un controlador recibe la solicitud (req) y la respuesta (res), procesa los datos y retorna una respuesta al cliente.
Un controlador bien diseñado sigue el principio de responsabilidad única: cada función hace una sola cosa y la hace bien. Esto facilita las pruebas unitarias, el mantenimiento y la reutilización del código.
Estructura Básica de un Controlador
// Controlador de usuarios - controllers/usuarioController.js
// GET /usuarios
const obtenerTodosLosUsuarios = async (req, res) => {
try {
const usuarios = await Usuario.findAll();
res.json({
success: true,
cantidad: usuarios.length,
data: usuarios
});
} catch (error) {
res.status(500).json({
success: false,
error: 'Error al obtener usuarios'
});
}
};
// GET /usuarios/:id
const obtenerUsuarioPorId = async (req, res) => {
try {
const { id } = req.params;
const usuario = await Usuario.findByPk(id);
if (!usuario) {
return res.status(404).json({
success: false,
error: 'Usuario no encontrado'
});
}
res.json({
success: true,
data: usuario
});
} catch (error) {
res.status(500).json({
success: false,
error: 'Error al obtener el usuario'
});
}
};
// POST /usuarios
const crearUsuario = async (req, res) => {
try {
const { nombre, email, password } = req.body;
// Validación básica
if (!nombre || !email || !password) {
return res.status(400).json({
success: false,
error: 'Faltan campos requeridos'
});
}
const nuevoUsuario = await Usuario.create({ nombre, email, password });
res.status(201).json({
success: true,
data: nuevoUsuario
});
} catch (error) {
res.status(500).json({
success: false,
error: 'Error al crear usuario'
});
}
};
module.exports = {
obtenerTodosLosUsuarios,
obtenerUsuarioPorId,
crearUsuario
};
Organización con Router de Express
Para aplicaciones más grandes, Express proporciona el objeto Router que te permite crear módulos de rutas independientes. Esto mantiene tu código organizado y escalable.
- Crear el archivo de rutas: Crea un archivo dedicado para cada recurso (usuarios, productos, pedidos, etc.)
- Importar Router: Importa
express.Router()en tu archivo de rutas - Definir rutas: Usa los métodos HTTP como harías con
app - Exportar el router: Exporta el router para usarlo en tu archivo principal
- Montar el router: En tu archivo principal, monta el router en una ruta base
// routes/usuarioRoutes.js
const express = require('express');
const router = express.Router();
const usuarioController = require('../controllers/usuarioController');
// Definir rutas
router.get('/', usuarioController.obtenerTodosLosUsuarios);
router.get('/:id', usuarioController.obtenerUsuarioPorId);
router.post('/', usuarioController.crearUsuario);
router.put('/:id', usuarioController.actualizarUsuario);
router.delete('/:id', usuarioController.eliminarUsuario);
module.exports = router;
// app.js - Archivo principal
const express = require('express');
const app = express();
const usuarioRoutes = require('./routes/usuarioRoutes');
const productoRoutes = require('./routes/productoRoutes');
// Montar routers en rutas base
app.use('/api/usuarios', usuarioRoutes);
app.use('/api/productos', productoRoutes);
app.listen(3000, () => {
console.log('Servidor corriendo en puerto 3000');
});
routes/, controllers/, models/ y middleware/. Esta organización facilita la colaboración en equipo y el mantenimiento a largo plazo.
Middleware en las Rutas
Una característica poderosa de Express es la capacidad de encadenar middleware en las rutas. El middleware se ejecuta antes del controlador final y puede modificar la solicitud, validar datos o verificar autenticación.
const verificarAutenticacion = require('../middleware/auth');
const validarDatosUsuario = require('../middleware/validacion');
// Aplicar middleware a rutas específicas
router.post('/',
verificarAutenticacion,
validarDatosUsuario,
usuarioController.crearUsuario
);
// Aplicar middleware a todas las rutas del router
router.use(verificarAutenticacion);
// Middleware específico para una ruta con parámetro
router.param('id', (req, res, next, id) => {
// Este middleware se ejecuta cuando hay un parámetro :id
req.idNumerico = parseInt(id, 10);
next();
});
console.log(req.originalUrl) y console.log(req.params) al inicio de tus controladores durante el desarrollo. Esto te ayuda a verificar exactamente qué datos está recibiendo tu API.
Manejo de Errores en Rutas
Es crucial manejar los errores de manera consistente. Express proporciona un middleware especial de manejo de errores que siempre toma cuatro parámetros:
// Middleware de manejo de errores (debe tener 4 parámetros)
app.use((err, req, res, next) => {
console.error(err.stack);
const statusCode = err.statusCode || 500;
const mensaje = err.message || 'Error interno del servidor';
res.status(statusCode).json({
success: false,
error: {
mensaje,
...(process.env.NODE_ENV === 'development' && { stack: err.stack })
}
});
});
Patrones Avanzados de Rutas
Ver más: Técnicas Avanzadas de EnrutamientoRutas con Expresiones Regulares
// Coincidir con rutas que contengan 'usuarios' o 'clientes'
app.get(/.*(usuarios|clientes)$/, controlador);
// Coincidir con IDs numéricos únicamente
app.get('/productos/:id([0-9]+)', controlador);
Rutas con Wildcards
// Coincidir con cualquier ruta que empiece con '/archivos'
app.get('/archivos/*', controlador);
// Capturar el resto de la ruta
app.get('/descargas/*path', (req, res) => {
console.log(req.params.path); // 'documentos/informe.pdf'
});
Grupos de Rutas
// Crear un grupo de rutas con prefijo común
const adminRouter = express.Router();
adminRouter.use((req, res, next) => {
// Middleware específico para admin
if (!req.user?.isAdmin) {
return res.status(403).json({ error: 'Acceso denegado' });
}
next();
});
adminRouter.get('/dashboard', controlador);
adminRouter.get('/usuarios', controlador);
adminRouter.delete('/usuarios/:id', controlador);
app.use('/admin', adminRouter);
"La arquitectura de rutas y controladores es como el sistema nervioso de tu aplicación Express. Una buena separación de responsabilidades hace que tu código sea más mantenible, testeable y escalable." — Best Practice de Desarrollo Express
Resumen y Buenas Prácticas
| Práctica | Recomendación |
|---|---|
| Nombrado de rutas | Usa sustantivos plurales: /usuarios, /productos |
| Organización | Separa routes, controllers y middleware en carpetas |
| Códigos de estado | Usa códigos HTTP apropiados: 200, 201, 400, 404, 500 |
| Validación | Siempre valida inputs en el controlador o middleware |
| Manejo de errores | Usa try/catch en funciones async y middleware de errores |
| Consistencia | Mantén un formato de respuesta consistente en toda la API |
¿Cuál es la diferencia principal entre PUT y PATCH en el contexto de una API RESTful?
- A) PUT es más rápido que PATCH
- B) PUT reemplaza completamente el recurso, mientras PATCH actualiza parcialmente
- C) PUT solo funciona con recursos existentes, PATCH crea nuevos
- D) No hay diferencia, son sinónimos
¿Qué sucede si defines dos rutas con el mismo método y path en Express?
- A) Express lanza un error de duplicación
- B) Solo se ejecuta la primera ruta definida
- C) Ambas rutas se ejecutan simultáneamente
- D) Express toma la ruta más reciente
express-validator para simplificar la validación de datos.