Sistema de rutas y controladores

Lectura
30 min~8 min lectura

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.

CONCEPTO CLAVE

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)
📌 Recuerda: El orden de definición de las rutas importa en Express. Las rutas más específicas deben definirse antes que las genéricas, de lo contrario Express podría nunca llegar a evaluarlas.

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
💡 Tip profesional: Utiliza 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 });
});
⚠️ Precaución: Los valores de 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.

CONCEPTO CLAVE

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.

  1. Crear el archivo de rutas: Crea un archivo dedicado para cada recurso (usuarios, productos, pedidos, etc.)
  2. Importar Router: Importa express.Router() en tu archivo de rutas
  3. Definir rutas: Usa los métodos HTTP como harías con app
  4. Exportar el router: Exporta el router para usarlo en tu archivo principal
  5. 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');
});
📌 Estructura recomendada: Mantén una estructura de carpetas consistente con carpetas separadas para 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();
});
💡 Tip de debugging: Usa 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 })
        }
    });
});
⚠️ Importante: El orden de los middleware importa. El middleware de manejo de errores siempre debe definirse al final, después de todas las rutas. Si lo colocas antes, Express no lo reconocerá como middleware de errores.

Patrones Avanzados de Rutas

Ver más: Técnicas Avanzadas de Enrutamiento

Rutas 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
🧠 Quiz

¿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
✅ Respuesta correcta: B) PUT reemplaza completamente el recurso con los datos proporcionados, mientras que PATCH se utiliza para actualizaciones parciales donde solo se envían los campos que se desean modificar.
🧠 Quiz

¿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
✅ Respuesta correcta: B) En Express, solo se ejecuta la primera ruta que coincide con la solicitud. Por eso es importante definir rutas específicas antes de las genéricas y ser consciente del orden de definición.
💡 Próximos pasos: En la siguiente lección aprenderás sobre middleware en Express, profundizando en cómo crear middlewares personalizados para autenticación, validación y manejo de errores centralizado. También exploraremos cómo usar bibliotecas como express-validator para simplificar la validación de datos.