Expresso: Como passar instância de aplicativo para rotas de um arquivo diferente?

103

Quero dividir minhas rotas em arquivos diferentes, onde um arquivo contém todas as rotas e o outro as ações correspondentes. Atualmente tenho uma solução para conseguir isso, porém preciso tornar a instância do aplicativo global para poder acessá-la nas ações. Minha configuração atual é assim:

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.

};

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

No entanto, essa configuração tem um grande problema: eu tenho um banco de dados e uma instância de aplicativo que preciso passar para as ações (controladores / *. Js). A única opção que eu poderia pensar é tornar as duas variáveis ​​globais, o que não é realmente uma solução. Quero separar as rotas das ações porque tenho muitas rotas e as quero em um local central.

Qual é a melhor maneira de passar variáveis ​​para as ações, mas separar as ações das rotas?

Claudio Albertin
fonte
Como é o seu controllers.js? Talvez você possa torná-lo uma função (em vez de um objeto) que pode receber parâmetros.
mihai
require ('controladores') requer controladores / index.js. No entanto, uma função não funciona porque eu uso o objeto nas rotas (consulte routes.js) e, portanto, não posso passar argumentos para ele, mesmo que seja uma função.
Claudio Albertin

Respostas:

165

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

A variável do aplicativo criada pela chamada express()é definida nos objetos de solicitação e resposta.

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

Feng
fonte
Obrigado. Acho que essa é a melhor maneira de acessar variáveis ​​definidas com app.set ('name', val);
Pavel Kostenko
4
Não se esqueça de chamar app.set('somekey', {})app.js
ankitjaininfo
3
Minha única reclamação sobre esse caminho, embora eu adore, é que quando você está tentando executar um app.locals.authorized como tal (não em main.js): app.route('/something').get(app.locals.authorized,function(req,res,next){});não é possível porque está fora do escopo de solicitação.
gabeio
Estou usando uma estratégia de passaporte diferente para um parâmetro de consulta diferente. Portanto, estou tentando definir passport.use ("nome-da-estratégia") em um middleware. Mesmo se eu armazenar o passaporte nesse middleware apenas com let passport = req.app, get ('passport'). Ele está sendo modificado para outro conjunto de solicitações. Por que é tão ?
Kartikeya Mishra
Se eu fizer isso, o objeto req terá instâncias adicionais de Object como redis e db no meu caso. Isso não afetará o desempenho do aplicativo? por exemplo: em index.js app.set ('redis', redis_client); em routes / example.js router = require ('express'). Router (); route.get ('/ test', (req, res, next) => {conosle.log (req.app.get ('redis')); return res.send ("// feito");})
Suz Aann Shrestha
101

Node.js oferece suporte a dependências circulares.
Usar dependências circulares em vez de require ('./ routes') (app) limpa uma grande quantidade de código e torna cada módulo menos interdependente de seu arquivo de carregamento:


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


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


----- Atualização de 04/2014 ----- O
Express 4.0 corrigiu o caso de uso para definir rotas adicionando um método express.router ()!
documentação - http://expressjs.com/4x/api.html#router

Exemplo de seu novo gerador:
Escrevendo a rota:
https://github.com/expressjs/generator/blob/master/templates/js/routes/index.js
Adicionando / namespacing ao aplicativo: https://github.com /expressjs/generator/blob/master/templates/js/app.js#L24

Ainda existem casos de uso para acessar o aplicativo de outros recursos, portanto, dependências circulares ainda são uma solução válida.

Will Stern
fonte
1
"menos interdependente de seu arquivo de carregamento" - depende do caminho de arquivo específico de seu arquivo de carregamento. Isso é um acoplamento muito forte, então não vamos fingir que não é.
Camilo Martin,
2
Basta ter muito cuidado (leia: não faça o que tenho lutado na última hora +) para app.jssolicitar o arquivo de roteamento após exportar o aplicativo. require()Chamadas circulares podem ser uma verdadeira bagunça, então certifique-se de saber como elas funcionam !
Nateowami
Sinceramente, acho que a resposta de @Feng sobre o uso de req.app.get ('somekey') é de fato uma solução muito melhor e mais limpa do que usar dependências circulr.
Claudio Mezzasalma
@Green se o aplicativo estiver vazio, você precisará de um arquivo que seja necessário appANTES do aplicativo module.exportsser definido. Você tem que instanciar app, definir e module.exports, em seguida, exigir arquivos que podem exigir. app Mas de qualquer maneira, fazer as dependências circulares é um antipadrão que o express resolveu - você não deve precisar mais fazer isso.
Will Stern
26

Como disse nos comentários, você pode usar uma função como module.exports. Uma função também é um objeto, então você não precisa alterar sua sintaxe.

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
fonte
É correto retornar um objeto dentro da função ou é melhor definir os métodos da maneira que você fez no seu exemplo?
Claudio Albertin
Eu acho que qualquer uma das abordagens está ok.
mihai
Como tenho muitos métodos, prefiro defini-los como um objeto em vez de cada um manualmente. Isso funcionaria quando eu apenas retornasse o objeto, mas não há uma solução um pouco mais plana? Meus métodos reais seriam indentados duas vezes ...
Claudio Albertin
Não tenho certeza se entendi você, mas acho que você poderia mover a implementação para fora dessa controllersfunção, algo como: jsfiddle.net/mihaifm/yV79K
mihai
os controladores / index.js não precisam retornar os controladores var?
Yalamber
5

Ou apenas faça isso:

var app = req.app

dentro do Middleware que você está usando para essas rotas. Curtiu isso:

router.use( (req,res,next) => {
    app = req.app;
    next();
});
Asanchez
fonte
Alguém me diga por que essa não é a resposta aceita? Para dependências que você usa app.use('my-service', serviceInstance)no roteador principal e req.app.get('my-service')no controlador conforme mencionado por @Feng
Felipe
0

Digamos que você tenha uma pasta chamada "contollers".

Em seu app.js, você pode colocar 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]");

... e ...

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

em seus controladores anteriores você terá o arquivo "ping.js" com este código:

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

E é isso ....

Radu Gheorghies
fonte
0
  1. Para tornar seu objeto db acessível a todos os controladores sem passá-lo para todos os lugares: faça um middleware de nível de aplicativo que anexa o objeto db a cada objeto req, então você pode acessá-lo em 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 passar instância de aplicativo em todos os lugares, em vez disso, passar rotas para onde o aplicativo está
// 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

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

Outra versão: você pode personalizar de acordo com suas necessidades, como adicionar solicitação de postagem

// 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
fonte
-1

Para banco de dados separado, serviço de acesso a dados que fará todo o trabalho do banco de dados com API simples e evitará estado compartilhado.

Separar routes.setup parece uma sobrecarga. Eu preferiria colocar um roteamento baseado em configuração em vez disso. E configure rotas em .json ou com anotações.

Eldar Djafarov
fonte
O que você quer dizer com serviço de acesso a dados? Como ficaria?
Claudio Albertin
Meu arquivo routes.js real é muito maior e usa o módulo express-namespaces. Como você separaria as rotas das ações?
Claudio Albertin