Express: Comment passer une instance d'application aux routes à partir d'un autre fichier?

103

Je veux diviser mes routes en différents fichiers, où un fichier contient toutes les routes et l'autre les actions correspondantes. J'ai actuellement une solution pour y parvenir, mais je dois rendre l'instance d'application globale pour pouvoir y accéder dans les actions. Ma configuration actuelle ressemble à ceci:

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');
});

routes.js:

exports.setup = function(app, controllers) {

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

};

controllers / index.js:

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

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

controllers / posts.js:

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

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

Cependant, cette configuration pose un gros problème: j'ai une base de données et une instance d'application que je dois transmettre aux actions (controllers / *. Js). La seule option à laquelle je pourrais penser est de rendre les deux variables globales, ce qui n'est pas vraiment une solution. Je veux séparer les itinéraires des actions car j'ai beaucoup d'itinéraires et je les veux dans un endroit central.

Quelle est la meilleure façon de transmettre des variables aux actions mais de séparer les actions des routes?

Claudio Albertin
la source
À quoi ressemble votre controllers.js? Peut-être que vous pouvez en faire une fonction (au lieu d'un objet) pouvant recevoir des paramètres.
mihai
require ('controllers') requiert controllers / index.js. Cependant, une fonction ne fonctionnera pas car j'utilise l'objet dans les routes (voir routes.js) et ne peux donc pas lui passer d'arguments, même s'il s'agit d'une fonction.
Claudio Albertin

Réponses:

165

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

La variable d'application créée par appel express()est définie sur les objets de requête et de réponse.

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

Feng
la source
Merci. Je pense que c'est la meilleure façon d'accéder aux variables définies avec app.set ('name', val);
Pavel Kostenko
4
N'oubliez pas d'appeler app.set('somekey', {})app.js
ankitjaininfo
3
Mon seul reproche à propos de cette façon, bien que j'adore cela, c'est que lorsque vous essayez d'exécuter un app.locals.authorized en tant que tel (pas dans main.js): app.route('/something').get(app.locals.authorized,function(req,res,next){});n'est pas possible car il est en dehors de la portée de req.
gabeio
J'utilise une stratégie de passeport différente pour différents paramètres de requête. J'essaye donc de placer passeport.use ("nom-stratégie") dans un middleware. Même si je stocke le passeport dans ce middleware uniquement avec let passport = req.app, get ('passeport'). Il est en cours de modification pour un autre ensemble de demandes. Pourquoi en est-il ainsi?
Kartikeya Mishra le
Si je fais cela, alors req object aura des instances Object supplémentaires comme redis et db dans mon cas. Cela n'affectera-t-il pas les performances de l'application? par exemple: dans index.js app.set ('redis', redis_client); dans routes / example.js router = require ('express'). Router (); route.get ('/ test', (req, res, next) => {conosle.log (req.app.get ('redis')); return res.send ("// done");})
Suz Aann shrestha
101

Node.js prend en charge les dépendances circulaires.
Utiliser des dépendances circulaires au lieu de require ('./ routes') (app) nettoie beaucoup de code et rend chaque module moins interdépendant de son fichier de chargement:


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


routes / 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');


----- Mise à jour 04/2014 -----
Express 4.0 a corrigé le cas d'utilisation pour définir les routes en ajoutant une méthode express.router ()!
documentation - http://expressjs.com/4x/api.html#router

Exemple de leur nouveau générateur:
Écriture de l'itinéraire:
https://github.com/expressjs/generator/blob/master/templates/js/routes/index.js
Ajout / namespacing à l'application: https://github.com /expressjs/generator/blob/master/templates/js/app.js#L24

Il existe encore des cas d'utilisation pour accéder à des applications à partir d'autres ressources, les dépendances circulaires restent donc une solution valide.

Will Stern
la source
1
"moins interdépendant de son fichier de chargement" - il dépend du chemin de fichier spécifique de son fichier de chargement. C'est un couplage très serré, alors ne prétendons pas que ce n'est pas le cas.
Camilo Martin
2
Faites juste très attention (lisez: ne faites pas ce avec quoi je me bats depuis une heure +) que app.jsvous ayez besoin du fichier de routage après l' exportation de l'application. Les require()appels circulaires peuvent créer un véritable gâchis, alors assurez-vous de savoir comment ils fonctionnent !
Nateowami
Je pense honnêtement que la réponse de @Feng à propos de l'utilisation de req.app.get ('somekey') est en effet une solution bien meilleure et plus propre que l'utilisation des dépendances circulr.
Claudio Mezzasalma
@Green si l'application est vide, vous avez besoin d'un fichier requis appAVANT que l'application ne module.exportssoit définie. Vous devez instancier app, définir module.exports, puis exiger des fichiers qui pourraient nécessiter app Mais de toute façon, faire les dépendances circulaires est un anti-modèle que Express a résolu - vous ne devriez plus avoir besoin de le faire.
Will Stern
26

Comme je l'ai dit dans les commentaires, vous pouvez utiliser une fonction comme module.exports. Une fonction est également un objet, vous n'avez donc pas à modifier votre syntaxe.

app.js

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

controllers.js

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

contrôleurs / index.js

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

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

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

module.exports = controllers;
Mihai
la source
Est-il correct de renvoyer un objet dans la fonction ou est-il préférable de définir les méthodes comme vous le faites dans votre exemple?
Claudio Albertin
Je pense que l'une ou l'autre approche est correcte.
mihai
Parce que j'ai beaucoup de méthodes, je préfère les définir comme un objet au lieu de chacune d'elles manuellement. Cela fonctionnerait lorsque je viens de renvoyer l'objet, mais n'y a-t-il pas une solution un peu plus plate? Mes méthodes réelles seraient indentées deux fois ...
Claudio Albertin
Je ne sais pas si je vous ai compris, mais je suppose que vous pouvez déplacer l'implémentation en dehors de cette controllersfonction, quelque chose comme: jsfiddle.net/mihaifm/yV79K
mihai
les contrôleurs / index.js n'ont-ils pas besoin de renvoyer les contrôleurs var?
Yalamber
5

Ou faites simplement ça:

var app = req.app

à l'intérieur du middleware que vous utilisez pour ces routes. Comme ça:

router.use( (req,res,next) => {
    app = req.app;
    next();
});
Asanchez
la source
Quelqu'un me dit pourquoi ce n'est pas la réponse acceptée? Pour les dépendances que vous utilisez app.use('my-service', serviceInstance)dans le routeur principal et req.app.get('my-service')dans le contrôleur comme mentionné par @Feng
Felipe
0

Disons que vous avez un dossier nommé "contollers".

Dans votre app.js, vous pouvez mettre ce code:

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]");

... et ...

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

dans vos contrôleurs forlder vous aurez le fichier "ping.js" avec ce code:

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

Et c'est ça ...

Radu Gheorghies
la source
0
  1. Pour rendre votre objet db accessible à tous les contrôleurs sans le passer partout: créez un middleware au niveau de l'application qui attache l'objet db à chaque objet req, puis vous pouvez y accéder dans chaque contrôleur.
// app.js
let db = ...;  // your db object initialized
const contextMiddleware = (req, res, next) => {
  req.db=db;
  next();
};
app.use(contextMiddleware);
  1. pour éviter de transmettre une instance d'application partout, à la place, en passant des routes vers l'emplacement de l'application
// 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

Le dernier app.js:

// 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');
});

Une autre version: vous pouvez personnaliser cela en fonction de vos propres besoins, comme l'ajout d'une demande de publication

// 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}));
souiller
la source
-1

Pour la base de données, séparez le service d'accès aux données qui effectuera tout le travail de base de données avec une API simple et évitera l'état partagé.

Séparer routes.setup ressemble à une surcharge. Je préférerais plutôt placer un routage basé sur la configuration. Et configurez les routes en .json ou avec des annotations.

Eldar Djafarov
la source
Que voulez-vous dire avec un service d'accès aux données? À quoi cela ressemblerait-il?
Claudio Albertin
Mon vrai fichier routes.js est beaucoup plus gros et utilise le module express-namespaces. Comment sépareriez-vous les itinéraires des actions?
Claudio Albertin