Organizar los ficheros de routes en nodejs – expressjs

Una de las tecnologías que estamos probando en Cartolab para aplicaciones web es nodejs con express como framework. Hay un montón de tutoriales de como empezar a usarlos, pero a poco que escribas empiezas a preguntarte como organizar el código, y ahí empiezan los problemas. express es un framework no opinativo, es decir proporciona un montón de utilidades pero da liberar total al usuario sobre como mezclarlas, así que se van desarrollando distintos modos de hacerlo.

La primera pregunta en mi casa sobre organizar el código fue acerca de los ficheros bajo el directorio routes. Podemos entender las routes de express como el equivalente al controlador en ese patrón llamado MVC que cada framework implementa como quiere, o viéndolo de otro modo como el mapeo entre una url y una función.

Vamos a ver cuatro estrategias distintas, cada una con sus ventajas e incovenientes.

Todo en app.js

La versión que se suele emplear en los tutoriales de iniciación. Escribimos en un único fichero todo el código de la aplicación.

  • Poco código boilerplate
  • Añadir una nueva url implica tocar un sólo fichero
  • Nada reusable
  • Sólo válido para proyectos pequeños

Es tan sencillo como escribir el mapeo antes de crear el servidor y usar funciones anónimas para la lógica


app.get('/', function(){res.send('root page'});

Mapear en app y la lógica en ficheros distintos

Este es el segundo ejemplo más habitual. Las url se definen en el fichero principal y las funcionalidades se agrupan en distintos ficheros bajo el directorio routes.

  • Añadir una nueva url implica tocar como mínimo dos ficheros
  • Favorece la reutilización, pero siempre debemos colocar las url a mano
  • Implica escribir más código que en el anterior y perder legibilidad

A pesar de que es muy habitual ver esto no me gusta porque no ganamos demasiado, y tener que hacer cambios en dos ficheros acaba introduciendo errores.


// app.js
var routes = require('./routes');  // Coje el fichero ./routes/index.js por defecto
var user = require('./routes/user');

app.get('/', routes.index);
app.get('/users', user.list);


// routes/user.js
exports.list = function(req, res){
res.send("respond with a resource");
};


// routes/index.js
exports.index = function(req, res){
res.send("root page");
};

Hacer el objeto app global y derivar todo hacia los ficheros de routes

Evitamos declarar app como variable local, para poder usarla en el resto de ficheros sin tener que preocuparnos de pasar parámetros. El código queda muy limpio pero se dificulta el testing y se pueden introducir bugs difíciles de detectar.

A mi me gusta esta aproximación cuando tenemos poco código y queremos hacer algo rápido, pero hay que ser consciente de los problemas que tiene.

  • No es una muy buena práctica hacer app global, en el siguiente punto vemos una mejora. Pero al hacerlo así obtenemos código más legible
  • Es bastante modular (excepto por hacer app global)
  • Es bastante legible
  • Hay separación de conceptos, cada fichero se encarga de unas determinadas url y funcionalidades

Referencias

Veamos como quedaría la implementación


// app.js
app = express(); //IMPORTANT! define the global app variable prior to requiring routes!
require('./routes');


// routes/index.js
require('./main');
require('./users');


// routes/main.js
app.get('/', function(req, res) {
res.send("root page");
});


// routes/users.js. list() could be an anonymous function, this is only for showing it in another way.

function list(req, res) {
res.send("user list");
};

app.get("/users", list);

Inyectar app en los ficheros de routes

Es similar al ejemplo anterior, pero el objeto principal es inyectado en los controladores en lugar de usarlo como una variable global. Sacrificamos un poco de legibilidad (hay que meter bastante código «inútil») pero a cambio ganamos en modularidad y testabilidad.

Veamos una posible implementación, teniendo en cuenta que este código no lo he visto en ningún sitio, lo he escrito a partir del artículo de dailyjs y podría tener algún otro problema.

A mi esta es la aproximación que más me gusta, cuando el código aumenta y no nos queremos ir a soluciones más complicadas.


// app.js
var app = express();
// ...
app.use(express.json());
require('./routes')(app); // Must be called after app.use(express.json()) and urlencoded;


// routes/index.js
module.exports = function(app) {
require('./main')(app);
require('./users')(app);
};


// routes/main.js
module.exports = function(app) {
function index(req, res) {
res.send("root page");
};
app.get('/', index);
};

Otras estrategias

Hay estrategias más complejas, que por ahora no me ha hecho falta probar.

Deja una respuesta