Express: ¿Cómo pasar la instancia de aplicación a rutas desde un archivo diferente?

103

Quiero dividir mis rutas en diferentes archivos, donde un archivo contiene todas las rutas y el otro las acciones correspondientes. Actualmente tengo una solución para lograr esto, sin embargo, necesito hacer que la instancia de la aplicación sea global para poder acceder a ella en las acciones. Mi configuración actual se ve así:

app.js:

var express   = require('express');
var app       = express.createServer();
var routes    = require('./routes');

var controllers = require('./controllers');
routes.setup(app, controllers);

app.listen(3000, function() {
  console.log('Application is listening on port 3000');
});

route.js:

exports.setup = function(app, controllers) {

  app.get('/', controllers.index);
  app.get('/posts', controllers.posts.index);
  app.get('/posts/:post', controllers.posts.show);
  // etc.

};

controladores / index.js:

exports.posts = require('./posts');

exports.index = function(req, res) {
  // code
};

controladores / posts.js:

exports.index = function(req, res) {
  // code
};

exports.show = function(req, res) {
  // code
};

Sin embargo, esta configuración tiene un gran problema: tengo una base de datos y una instancia de aplicación que necesito pasar a las acciones (controladores / *. Js). La única opción que se me ocurre es hacer que ambas variables sean globales, lo cual no es realmente una solución. Quiero separar las rutas de las acciones porque tengo muchas rutas y las quiero en un lugar central.

¿Cuál es la mejor manera de pasar variables a las acciones pero separar las acciones de las rutas?

Claudio Albertin
fuente
¿Cómo se ve su controllers.js? Tal vez pueda convertirlo en una función (en lugar de un objeto) que pueda recibir parámetros.
mihai
require ('controladores') requiere controladores / index.js. Sin embargo, una función no funcionará porque utilizo el objeto en las rutas (ver routes.js) y, por lo tanto, no puedo pasarle argumentos, incluso si es una función.
Claudio Albertin

Respuestas:

165

uso req.app,req.app.get('somekey')

La variable de aplicación creada por la llamada express()se establece en los objetos de solicitud y respuesta.

Ver: https://github.com/visionmedia/express/blob/76147c78a15904d4e4e469095a29d1bec9775ab6/lib/express.js#L34-L35

Feng
fuente
Gracias. Creo que esta es la mejor forma de acceder a las variables establecidas con app.set ('nombre', val);
Pavel Kostenko
4
No olvide llamar app.set('somekey', {})app.js
ankitjaininfo
3
Mi única queja sobre esta forma, aunque me encanta, es que cuando intentas ejecutar un app.locals.authorized como tal (no en main.js): app.route('/something').get(app.locals.authorized,function(req,res,next){});no es posible porque está fuera del alcance de req.
gabeio
Estoy usando una estrategia de pasaporte diferente para diferentes parámetros de consulta. Así que estoy intentando configurar passport.use ("nombre de estrategia") en un middleware. Incluso si guardo el pasaporte en ese middleware solo con let passport = req.app, get ('passport'). Se está modificando para otro conjunto de solicitudes. Por que es esto entonces ?
Kartikeya Mishra
Si hago esto, entonces el objeto req tendrá instancias de objetos adicionales como redis y db en mi caso. ¿No afectará el rendimiento de la aplicación? por ejemplo: en index.js app.set ('redis', redis_client); en rutas / ejemplo.js router = require ('express'). Router (); route.get ('/ test', (req, res, next) => {conosle.log (req.app.get ('redis')); return res.send ("// hecho");})
Suz Aann shrestha
101

Node.js admite dependencias circulares.
Hacer uso de dependencias circulares en lugar de require ('./ routes') (app) limpia una gran cantidad de código y hace que cada módulo sea menos interdependiente de su archivo de carga:


app.js

var app = module.exports = express(); //now app.js can be required to bring app into any file

//some app/middleware setup, etc, including 
app.use(app.router);

require('./routes'); //module.exports must be defined before this line


rutas / index.js

var app = require('../app');

app.get('/', function(req, res, next) {
  res.render('index');
});

//require in some other route files...each of which requires app independently
require('./user');
require('./blog');


----- Actualización 04/2014 -----
Express 4.0 corrigió el caso de uso para definir rutas agregando un método express.router ().
documentación: http://expressjs.com/4x/api.html#router

Ejemplo de su nuevo generador:
Escribiendo la ruta:
https://github.com/expressjs/generator/blob/master/templates/js/routes/index.js
Agregando / espacio de nombres a la aplicación: https://github.com /expressjs/generator/blob/master/templates/js/app.js#L24

Todavía hay casos de uso para acceder a la aplicación desde otros recursos, por lo que las dependencias circulares siguen siendo una solución válida.

Will Stern
fuente
1
"menos interdependiente de su archivo de carga": depende de la ruta de archivo específica de su archivo de carga. Eso es un acoplamiento muy estrecho, así que no pretendamos que no lo sea.
Camilo Martin
2
Solo tenga mucho cuidado (lea: no haga lo que he estado luchando durante la última hora +) que app.jsnecesita el archivo de enrutamiento después de exportar la aplicación. Las require()llamadas circulares pueden ser un verdadero desastre, ¡así que asegúrese de saber cómo funcionan !
Nateowami
Honestamente, creo que la respuesta de @Feng sobre el uso de req.app.get ('somekey') es de hecho una solución mucho mejor y más limpia que el uso de dependencias circulr.
Claudio Mezzasalma
@Verde si la aplicación está vacía, necesita un archivo que se requiere appANTES de module.exportsdefinir la aplicación . Debe crear una instancia app, configurar module.exportsy luego requerir archivos que podrían requerir appPero de cualquier manera, hacer las dependencias circulares es un anti-patrón que Express ha resuelto, ya no debería necesitar hacer eso.
Will Stern
26

Como dije en los comentarios, puede usar una función como module.exports. Una función también es un objeto, por lo que no tiene que cambiar su sintaxis.

app.js

var controllers = require('./controllers')({app: app});

controllers.js

module.exports = function(params)
{
    return require('controllers/index')(params);
}

controladores / index.js

function controllers(params)
{
  var app = params.app;

  controllers.posts = require('./posts');

  controllers.index = function(req, res) {
    // code
  };
}

module.exports = controllers;
mihai
fuente
¿Está bien devolver un objeto dentro de la función o es mejor configurar los métodos como lo hace en su ejemplo?
Claudio Albertin
Creo que cualquier enfoque está bien.
mihai
Como tengo muchos métodos, preferiría establecerlos como un objeto en lugar de cada uno manualmente. Esto funcionaría cuando solo devuelva el objeto, pero ¿no hay una solución que sea un poco más plana? Mis métodos reales se sangrarían dos veces ...
Claudio Albertin
No estoy seguro si he entendido, pero supongo que se podría mover la parte exterior de implementación que controllersla función, algo así como: jsfiddle.net/mihaifm/yV79K
Mihai
¿No es necesario que los controladores / index.js devuelvan controladores var?
Yalamber
5

O simplemente haz eso:

var app = req.app

dentro del Middleware que está utilizando para estas rutas. Como eso:

router.use( (req,res,next) => {
    app = req.app;
    next();
});
asanchez
fuente
Alguien me diga por qué esta no es la respuesta aceptada. Para las dependencias que usa app.use('my-service', serviceInstance)en el enrutador principal y req.app.get('my-service')en el controlador como lo menciona @Feng
Felipe
0

Digamos que tiene una carpeta llamada "controladores".

En su app.js puede poner este código:

console.log("Loading controllers....");
var controllers = {};

var controllers_path = process.cwd() + '/controllers'

fs.readdirSync(controllers_path).forEach(function (file) {
    if (file.indexOf('.js') != -1) {
        controllers[file.split('.')[0]] = require(controllers_path + '/' + file)
    }
});

console.log("Controllers loaded..............[ok]");

... y ...

router.get('/ping', controllers.ping.pinging);

en sus controladores, más adelante tendrá el archivo "ping.js" con este código:

exports.pinging = function(req, res, next){
    console.log("ping ...");
}

Y esto es ...

Radu Gheorghies
fuente
0
  1. Para hacer que su objeto db sea accesible para todos los controladores sin pasarlo a todas partes: cree un middleware a nivel de aplicación que adjunte el objeto db a cada objeto req, luego podrá acceder a él en cada controlador.
// app.js
let db = ...;  // your db object initialized
const contextMiddleware = (req, res, next) => {
  req.db=db;
  next();
};
app.use(contextMiddleware);
  1. para evitar pasar la instancia de la aplicación a todas partes, en su lugar, pasar rutas a donde está la aplicación
// routes.js  It's just a mapping.
exports.routes = [
  ['/', controllers.index],
  ['/posts', controllers.posts.index],
  ['/posts/:post', controllers.posts.show]
];

// app.js
var { routes }    = require('./routes');
routes.forEach(route => app.get(...route));
// You can customize this according to your own needs, like adding post request

El app.js final:

// app.js
var express   = require('express');
var app       = express.createServer();

let db = ...;  // your db object initialized
const contextMiddleware = (req, res, next) => {
  req.db=db;
  next();
};
app.use(contextMiddleware);

var { routes }    = require('./routes');
routes.forEach(route => app.get(...route));

app.listen(3000, function() {
  console.log('Application is listening on port 3000');
});

Otra versión: puede personalizar esto según sus propias necesidades, como agregar una solicitud de publicación

// routes.js  It's just a mapping.
let get = ({path, callback}) => ({app})=>{
  app.get(path, callback);
}
let post = ({path, callback}) => ({app})=>{
  app.post(path, callback);
}
let someFn = ({path, callback}) => ({app})=>{
  // ...custom logic
  app.get(path, callback);
}
exports.routes = [
  get({path: '/', callback: controllers.index}),
  post({path: '/posts', callback: controllers.posts.index}),
  someFn({path: '/posts/:post', callback: controllers.posts.show}),
];

// app.js
var { routes }    = require('./routes');
routes.forEach(route => route({app}));
manchar
fuente
-1

Para la base de datos, separe el servicio de acceso a datos que hará todo el trabajo de la base de datos con una API simple y evitará el estado compartido.

La instalación de rutas de separación parece un techo. En su lugar, preferiría colocar un enrutamiento basado en la configuración. Y configura rutas en .json o con anotaciones.

Eldar Djafarov
fuente
¿Qué quiere decir con un servicio de acceso a datos? ¿Cómo se vería?
Claudio Albertin
Mi archivo route.js real es mucho más grande y usa el módulo express-namespaces. ¿Cómo separarías las rutas de las acciones?
Claudio Albertin