【问题标题】:Express: How to pass app-instance to routes from a different file?Express:如何将 app-instance 传递给来自不同文件的路由?
【发布时间】:2012-04-22 20:11:59
【问题描述】:

我想将我的路线分成不同的文件,其中一个文件包含所有路线,另一个文件包含相应的操作。我目前有一个解决方案来实现这一点,但是我需要将 app-instance 设为全局才能在操作中访问它。 我当前的设置如下所示:

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.

};

控制器/index.js:

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

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

控制器/posts.js:

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

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

但是,此设置有一个大问题:我需要将一个数据库和一个应用程序实例传递给操作(控制器/*.js)。我能想到的唯一选择是将两个变量都设为全局变量,这并不是真正的解决方案。我想将路线与动作分开,因为我有很多路线并且希望它们位于中心位置。

将变量传递给动作但将动作与路由分开的最佳方式是什么?

【问题讨论】:

  • 你的 controllers.js 看起来怎么样?也许你可以把它做成一个可以接收参数的函数(而不是一个对象)。
  • require('controllers') 需要控制器/index.js。但是,函数不起作用,因为我在路由中使用了对象(请参阅 routes.js),因此即使它是函数,也无法将参数传递给它。

标签: node.js express


【解决方案1】:

使用req.appreq.app.get('somekey')

通过调用express() 创建的应用程序变量设置在请求和响应对象上。

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

【讨论】:

  • 谢谢。我认为这是访问用 app.set('name', val); 设置的变量的最佳方式;
  • 别忘了在 app.js 中调用app.set('somekey', {})
  • 虽然我很喜欢这种方式,但我唯一的抱怨是,当您尝试运行 app.locals.authorized 时(不在 main.js 中):app.route('/something').get(app.locals.authorized,function(req,res,next){}); 是不可能的,因为它在 req 范围之外。
  • 我对不同的查询参数使用不同的护照策略。所以我试图在中间件中设置 passport.use("strategy-name") 。即使我仅使用 let passport = req.app,get('passport') 将护照存储在该中间件中。它正在针对另一组请求进行修改。为什么会这样?
  • 如果我这样做,那么在我的情况下,req 对象将具有额外的对象实例,例如 redis 和 db。它不会影响应用程序性能吗?例如:在 index.js app.set('redis',redis_client);在 routes/example.js 中路由器 = require('express').Router(); route.get('/test',(req,res,next)=>{ conosle.log(req.app.get('redis')); return res.send("//done"); })
【解决方案2】:

Node.js 支持循环依赖。
使用循环依赖而不是 require('./routes')(app) 可以清理大量代码并减少每个模块的相互依赖在它的加载文件上:


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


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


-----04/2014 更新-----
Express 4.0 通过添加 express.router() 方法修复了定义路由的用例!
文档 - http://expressjs.com/4x/api.html#router

来自他们的新生成器的示例:
写路线:
https://github.com/expressjs/generator/blob/master/templates/js/routes/index.js
将其添加/命名空间到应用程序: https://github.com/expressjs/generator/blob/master/templates/js/app.js#L24

仍然存在从其他资源访问应用程序的用例,因此循环依赖仍然是一个有效的解决方案。

【讨论】:

  • "与其加载文件的相互依赖较少" - 它取决于其加载文件的特定 filepath。这是非常紧密的耦合,所以我们不要假装它不是。
  • app.js 中需要非常小心(阅读:不要做我过去一小时一直在努力的事情),您需要路由文件导出之后应用程序。循环require() 电话可能会造成一团糟,所以请确保您知道how they work
  • 老实说,我认为@Feng 关于使用 req.app.get('somekey') 的答案确实是比使用循环依赖项更好、更清洁的解决方案。
  • @Green 如果应用程序为空,则在定义应用程序module.exports 之前,您需要一个需要app 的文件。你必须实例化app,设置module.exports,然后需要可能需要app 的文件但是无论哪种方式,执行循环依赖都是express 解决的反模式——你不应该再这样做了。
【解决方案3】:

就像我在 cmets 中所说,您可以将函数用作 module.exports。函数也是对象,因此您不必更改语法。

app.js

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

controllers.js

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

控制器/index.js

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

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

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

module.exports = controllers;

【讨论】:

  • 在函数内部返回一个对象是否可以,或者最好按照您在示例中的方式设置方法?
  • 我认为这两种方法都可以。
  • 因为我有很多方法,我更喜欢将它们设置为一个对象,而不是手动设置。当我只返回对象时,这会起作用,但是没有更平坦的解决方案吗?我的实际方法会缩进两次...
  • 不确定我是否理解你,但我想你可以将实现移到 controllers 函数之外,例如:jsfiddle.net/mihaifm/yV79K
  • controllers/index.js不需要返回controllers var吗?
【解决方案4】:

或者只是这样做:

var app = req.app

在您用于这些路由的中间件内部。像这样:

router.use( (req,res,next) => {
    app = req.app;
    next();
});

【讨论】:

  • 有人告诉我为什么这不是公认的答案?对于依赖项,您在主路由器中使用app.use('my-service', serviceInstance),在控制器中使用req.app.get('my-service'),如@Feng 所述
  • 如果您在myMiddlewareFile.js 中使用var app = req.app 并使用app.locals.mongodb_client = mongdb_client 之类的内容“更新它”,您是否需要从该文件中导出app 以使该属性可供其他人访问中间件文件?还是 app 从该分配中“全局”更新?
【解决方案5】:

假设您有一个名为“contollers”的文件夹。

在您的 app.js 中,您可以输入以下代码:

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

...和...

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

在您的控制器中,您将拥有包含以下代码的文件“ping.js”:

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

就是这样……

【讨论】:

    【解决方案6】:
    1. 要让所有控制器都可以访问您的 db 对象而不将其传递到任何地方:制作一个应用程序级中间件,将 db 对象附加到每个 req 对象,然后您可以在每个控制器中访问它。
    // app.js
    let db = ...;  // your db object initialized
    const contextMiddleware = (req, res, next) => {
      req.db=db;
      next();
    };
    app.use(contextMiddleware);
    
    1. 为了避免到处传递应用实例,而是将路由传递到应用所在的位置
    // 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
    

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

    另一个版本:你可以根据自己的需要自定义这个,比如添加post请求

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

    【讨论】:

      【解决方案7】:

      如果您想在 Node-Typescript 中将应用实例传递给其他人:

      选项 1:import 的帮助下(导入时

      //routes.ts
      import { Application } from "express";
      import { categoryRoute } from './routes/admin/category.route'
      import { courseRoute } from './routes/admin/course.route';
      
      const routing = (app: Application) => {
          app.use('/api/admin/category', categoryRoute)
          app.use('/api/admin/course', courseRoute)
      }
      export { routing }
      

      然后导入并传递app:

      import express, { Application } from 'express';
      
      const app: Application = express();
      import('./routes').then(m => m.routing(app))
      

      选项2:借助class

      // index.ts
      import express, { Application } from 'express';
      import { Routes } from './routes';
      
      
      const app: Application = express();
      const rotues = new Routes(app)
      ...
      

      这里我们将在Routes Class的构造函数中访问应用程序

      // routes.ts
      import { Application } from 'express'
      import { categoryRoute } from '../routes/admin/category.route'
      import { courseRoute } from '../routes/admin/course.route';
      
      class Routes {
          constructor(private app: Application) {
              this.apply();
          }
      
          private apply(): void {
             this.app.use('/api/admin/category', categoryRoute)
             this.app.use('/api/admin/course', courseRoute)
          }
      }
      
      export { Routes }
      

      【讨论】:

        【解决方案8】:

        对于数据库分离数据访问服务,它将使用简单的 API 完成所有数据库工作并避免共享状态。

        分离 routes.setup 看起来像是开销。我宁愿放置基于配置的路由。并在 .json 或注解中配置路由。

        【讨论】:

        • 数据访问服务是什么意思?它会是什么样子?
        • 我真正的 routes.js 文件要大得多,并且使用 express-namespaces 模块。您如何将路线与动作分开?
        猜你喜欢
        • 1970-01-01
        • 2017-09-11
        • 2019-11-05
        • 1970-01-01
        • 1970-01-01
        • 2016-06-17
        • 2020-02-09
        • 1970-01-01
        • 2023-03-27
        相关资源
        最近更新 更多