Separación de responsabilidades con express.Router
Resumen
¿Qué es el principio de única responsabilidad?
En el mundo del desarrollo de software, el principio de única responsabilidad, conocido como Single Responsibility Principle (SRP), juega un rol crucial. Este principio establece que cada artefacto o pieza de código debe tener una única responsabilidad. Esto se aplica no solo a métodos, sino también a clases y archivos. Por ejemplo, un método denominado sumar debería estar dedicado exclusivamente a realizar sumas, sin encargarse de ecuaciones complejas u otras operaciones matemáticas.
¿Cómo aplicar este principio en archivos y carpetas?
La práctica de aplicar el principio de única responsabilidad se extiende a la organización de archivos y carpetas dentro de una aplicación. Un enfoque útil es dividir el código basado en la funcionalidad específica de cada parte. Por ejemplo, en una aplicación, cada endpoint podría corresponder a un archivo específico que defina todas las rutas necesarias para ese dominio, como productos o categorías.
¿Cuál es la importancia del nombre de los archivos?
El proceso de nombrar archivos debe seguir un estándar dentro del equipo de desarrollo. Aunque las normas pueden variar dependiendo del equipo, lo importante es la consistencia. Por ejemplo, si estamos trabajando en la carpeta Routes, un archivo nombrado Products.js indica claramente que trata sobre las rutas asociadas a los productos. Alternativamente, se puede usar una nomenclatura más descriptiva como Products.router.js, siempre y cuando el equipo acuerde un estándar uniforme.
¿Cómo implementar el principio en Node.js?
A continuación se presenta un ejemplo de implementación del principio de única responsabilidad al trabajar con rutas en Node.js y Express:
Configuración de rutas específicas
En este fragmento de código, se crea y configura un archivo de rutas para los productos. Esta acción separa la lógica de rutas en módulos individuales:
// Importar Express y Fakerconst express =require('express');const faker =require('faker');// Crear un router específico para productosconst router = express.Router();// Definir rutas específicas sin el endpoint completorouter.get('/',(req, res)=>{ res.json({products:'List of products'});});// Exportar el router como un módulomodule.exports= router;
Centralización y configuración de rutas
En el archivo index.js, se integran todos estos routers modulares para establecer una arquitectura clara:
// Importar el router de productosconst productsRouter =require('./products.router');// Función para configurar routers en la aplicaciónfunctionroutesApi(app){// Configurar el endpoint para productos app.use('/api/products', productsRouter);}// Exportar la función de configuraciónmodule.exports= routesApi;
Por último, en el archivo principal de la aplicación, se invoca esta función para inicializar la configuración de rutas:
const express =require('express');const routesApi =require('./routes');const app =express();const port =3000;// Llamar a la función para configurar las rutasroutesApi(app);app.listen(port,()=>{console.log('Server running on port', port);});
¿Qué beneficios aporta el principio de única responsabilidad?
Legibilidad: Facilita la comprensión del código ya que cada archivo o método tiene un propósito claro y definido.
Mantenibilidad: Al separar la lógica, es más fácil realizar cambios o actualizaciones sin afectar otras partes del sistema.
Reutilización: Los módulos bien definidos son más fáciles de reutilizar en otros proyectos o contextos.
Al aplicar este principio, se optimiza la estructura del código, lo cual es esencial para el desarrollo de aplicaciones escalables y fáciles de mantener. Te animamos a continuar practicando estos conceptos para perfeccionar tus habilidades como desarrollador.
Ya maneje la organización de manera distinta usando clases en typescript, y dentro de las clases manejo los middlewares, routes o listen del servidor mediante métodos.
Mi estructura de directorios es algo así
el index.ts ubicado en el directorio raíz inicialice la clase y posteriormente ejecuta el método listen
import{Server}from"./app/server"const server =newServer();server.listen()
y el index.ts dentro de app/server/ se encuentra la clase donde se ejecuta express
y para las rutas es muy similar a como lo realiza el instructor, ejemplo para products.ts
import{Router}from"express";importfakerfrom"faker";const router =Router();router.get("/",(req, res)=>{const{ size }= req.query;const products =[];const limit = size ||100;for(let index =0; index < limit; index++){ products.push({name: faker.commerce.productName(),price:parseInt(faker.commerce.price(),10),image: faker.image.imageUrl(),});} res.send(products);});router.get("/filter",(req, res)=>{ res.json({msg:"Soy un filtro"});});router.get("/:id",(req, res)=>{const{ id }= req.params; res.send({ id,name:"product1",price:"1000"});});exportdefault router;
Hola michael, yo también venía trabajando de esta manera, pero ví un blog donde decía que esta manera no se trabajaba del todo bien, para mejorarlo se debería estructurar por temas, ejemplo:
tienes una carpeta users, y en ella pones las carpetas controllers, routes, models etc, carpetas y archivos que solo tengan que ver con usuarios.
Hola Michael, lo que comenta Gustavo es cierto. Como lo comentas no es la mejor manera de trabajar.
En el curso de backend con base de datos con Mongo y websockets comentan un esquema recomendado como buena práctica para que el proyecto sea escalable.
Amigos, acá unos conceptos que le pueden aclarar dudas.
Express.Router
Crea un controlador(handler) de rutas modulares y montables. Una instancia de Router es un sistema de enrutamiento y middleware completo, por esa razón lo podemos tomar como si fuera una mini app.
Cada modulo de nuestras rutas es una mini aplicación en la que creamos sus rutas independientes y podemos incluirle middlewares, que se ejecutarán cuando se coincida con el path.
Qué es un middleware?
Un middleware es un bloque de código que se ejecuta entre la petición que hace el usuario (request) hasta que la petición llega al servidor.
Fuente:
Middlewares en Node.js
App.use
app.use Lo que hace es montar un middleware en la ruta especificada. Por ejemplo:
var express =require('express')var router = express.Router()// middleware específico a este routerrouter.use('/',function(req, res, next){console.log('Hola, soy el middleware')next()// se utiliza para que se ejecute el router.get})// define the home page routerouter.get('/',function(req, res){ res.send('Birds home page')})// define the about routerouter.get('/about',function(req, res){ res.send('About birds')})module.exports= router
Liskov Substitution: Por ejemplo, cuando creamos una array, en Java sería List<Integer> nums = new ArrayList<>();. ArrayList implementa List (una interfaz o padre), por lo tanto al llamar los metodos de list estaremos llamando a los metodos implementados en ArrayList.
Interface segregation: Por ejemplo, una interfaz ‘Television’ no debería tener un método llamado ‘transmitirEn(Laptop)’.
Dependency Inversion: Por ejemplo, en vez enviar por parámetro un objeto específico (ej: new Corporacion(Ingeniero ing) ), debemos enviar un objeto más abstracto para que nuestra funcionalidad no este conectada a un tipo específico ya que esto puede causar problemas a la hora de arreglar bugs o añadir más features.
Mi solución
Me encanta como lo resolviste!!
Que herramienta utilizas para que se vea así ? 😄
Esta clase me encanto, lo dejo todo muy claro y muy bien explicado, llevaba meses preguntándome como debería yo hacer esto?! y hasta que por fin!! ahora me siento como que estoy apunto de Graduarme! jajaja
Me parece que primero deberían haber introducido el concepto de Middlewares antes de brindar esta clase. Creo que el orden no es correcto.
TIP para quién lo esté haciendo con module y no common. (usando IMPORTS xxx from "./algo") y no los "const data = require('algo')"
El final del path debe terminar en .js y los archivos "index" también deben ser escritos y finalizados en .js => import routerApi from "./routes/index.js" de lo contrario crashea.
Mi solución
Que buenas recomendaciones sobre las formas de organizar el proyecto!
Single responsability principle
El principio de responsabilidad única o SRP en ingeniería de software establece que cada módulo o clase debe tener responsabilidad sobre una sola parte de la funcionalidad
.
para nuestro proyecto separaremos las responsabilidades por cada entidad, crearemos una nueva carpeta routes en la raíz con archivos entidad.router.js por cada entidad
.
estos archivos tendrán su propio router por entidad con todas sus rutas correspondientes y serán exportados cada uno como modulo, la idea es crear un enrutador aparte por cada entidad y después inyectarlo a la aplicación
routes/product.router.js:
const express =require('express')const{ faker }=require('@faker-js/faker')const router = express.Router()router.get('/',(req,res)=>{const{ limit =10}= req.queryconst products =[]for(let i =0; i < limit; i++){ products.push({name: faker.commerce.productName(),price: faker.commerce.price(),image: faker.image.imageUrl()})} res.json(products)})router.get('/:id',(req,res)=>{const{ id }= req.params res.json({ id,})})module.exports= router
routes/categories.router.js:
const express =require('express')const{ faker }=require('@faker-js/faker')const router = express.Router()router.get('/',(req,res)=>{const{ limit =10}= req.queryconst categories =[]for(let i =0; i < limit; i++){ categories.push({name: faker.random.word()})} res.json(categories)})router.get('/:id',(req,res)=>{const{ id }= req.params res.json({ id,})})module.exports= router
luego creamos un archivo principal con nombre index.js dentro de la carpeta routes, que importa el router de cada una de las entidades y contiene una función routerApi que recibe como parámetro el server o aplicación y hace uso del router importado de una entidad u otra dependiendo el inicio de la url visitada y será exportada como un modulo
routes/index.js:
como ultimo paso, en nuestro index.js que se encuentra en la raíz del proyecto, debemos importar esta función routerApi anteriormente creada, ejecutarla y pasarle por parámetro el server o aplicación inicializada con Express que se encuentra escuchando en el puerto seleccionado
index.js:
const express =require('express')const app =express()const routerApi =require('./routes')routerApi(app)const port =3000app.listen(port,()=>{console.log(`Listening on port${port}`)})
si visitamos la url "/api/products" veremos como respuesta de parte del servidor un arreglo con 10 productos
si visitamos la url "/api/users?limit=3" veremos como respuesta de parte del servidor un arreglo con 3 usuarios
[{"firstName":"Dayne","lastName":"Moore","gender":"Xenogender","jobTitle":"Principal Data Producer"},{"firstName":"Sterling","lastName":"Hane","gender":"T* woman","jobTitle":"Future Quality Specialist"},{"firstName":"Liza","lastName":"Mann","gender":"Woman","jobTitle":"Direct Functionality Administrator"}]
si visitamos la url "/api/users/1" veremos como respuesta de parte del servidor en JSON con el numero de id pasado
{"id":"1"}
de esta forma estaríamos modularizando la aplicación, y conectando todo de tal forma que cada entidad tenga su propio router con sus propias rutas y sea responsable de gestionar solo lo correspondiente a esa entidad, sea los productos, categorías, usuarios etc.
Mi solución al momento
index.js
routes/index
db/init.db
routes/products.router
Les comparto que agregue las rutas usuarios y categorías:
Desde faker es posible generar usuarios, es muy interesante lo que se puede generar con éste módulo, les comparto el link de donde pueden obtener más información: faker npm
Porque se utiliza Javascript y no ECMAScript 6 en el curso?
Porque Node aún no soporta de forma nativa ES6 como por ejemplo usar export / import etc así que aún nos toca de esta manera a menos que uses cosas como babel o TS que luego transpilen el código, esperamos en que futuras versiones de Node ya lo soporte de forma nativa, en NodeJS 12 hay una forma de habilitarlo desde el package.json con el attributo type en donde le pones nextnode.
He visto que muchos developers usar src como archivo, no se sí hay alguna diferencia.
Yo lo escribí así
Hola!
Yo he visto que los archivos principales los llaman index.js o main.js y se ponen dentro de la carpeta src/. No creo que cambie el significado del archivo si le pones otro nombre, pero debe ser por convención que se usan esos nombres.