express.Router和app.get之间的区别是什么?

393

我正在学习NodeJS和Express 4,但有点困惑。我一直在阅读Express网站,但无法确定何时使用路由处理程序或何时使用express.Router

据我所知,如果我想要在用户访问例如/show时显示页面或其他内容,我应该使用:

var express = require('express')    
var app = express()    
app.get("/show", someFunction)  

一开始,我认为这是旧的(适用于Express 3)。这是正确的吗?还是在Express 4中也是这样做的方式?

如果这是在Express 4中进行操作的方式,那么express.Router有什么用?

我读到了几乎与上面相同的示例,但是使用了express.Router

var express = require('express');
var router = express.Router();
router.get("/show", someFunction)

那么,这两个示例之间有什么区别呢?

如果我只想要一个简单的测试网站,我应该使用哪一个?


47
一个Router本身不会.listen()来处理请求。它有助于将你的应用程序分成多个模块--在每个模块中创建一个Router,然后通过app进行require().use()作为中间件。 - Jonathan Lonowski
8
正如@JonathanLonowski所暗示的那样,app.get(..)语法只是一个快捷方式,使得使用express.router更加方便。如果你刚开始学习,不要担心路由器的细节。 - soulprovidr
1
所以你的意思是我现在只应该使用app.get()吗?我仍然困惑于何时使用其中之一。 - nelson687
16
@nelson687 之间没有一个确定的硬性规则。如果您觉得app自己的路由方法(例如app.get())足以满足您的需求,请使用它们。Router只是为了方便您在多个模块上组织应用程序而存在的。根据指南所述:“*express.Router类可用于创建模块化可挂载的路由处理程序。Router实例是完整的中间件和路由系统,因此通常被称为“mini-app”。*” - Jonathan Lonowski
显示剩余3条评论
10个回答

477

app.js

var express = require('express'),
    dogs    = require('./routes/dogs'),
    cats    = require('./routes/cats'),
    birds   = require('./routes/birds');

var app = express();

app.use('/dogs',  dogs);
app.use('/cats',  cats);
app.use('/birds', birds);

app.listen(3000);

dogs.js

var express = require('express');

var router = express.Router();

router.get('/', function(req, res) {
    res.send('GET handler for /dogs route.');
});

router.post('/', function(req, res) {
    res.send('POST handler for /dogs route.');
});

module.exports = router;
当调用var app = express()时,将返回一个app对象,它是主应用程序。当调用var router = express.Router()时,将返回一个略微不同的mini app。mini app的想法是您的应用程序中的每个路由可能会变得非常复杂,并且从将所有代码移动到单独的文件中会受益。每个文件的路由器变成了一个mini app,其结构与主应用程序非常相似。在上面的示例中,/dogs路由的代码已转移到其自己的文件中,因此不会混乱主应用程序。对于/cats/birds的代码也同样类似地分别放在它们自己的文件中。通过将此代码分成三个mini app,您可以隔离地处理每个逻辑而不必担心它将如何影响其他两个。如果您有适用于所有三条路由的代码(middleware),则可以将其放在app.use(...)之前的主应用程序中。如果您有适用于其中一条路由的代码(middleware),则可以将其仅放在该路由的文件中。

2
不需要将应用程序传递到路由中,因为路由已经通过 app.use('/dogs', show) 传递给了应用程序。这样,路由就独立于应用程序,并且可以在任何 Express 应用程序中重复使用。在路由之前放置的中间件将被该路由使用。如果您将中间件放置在 app.js 中的所有路由之上,则所有路由都将使用该中间件。如果您将中间件放置在路由文件(dogs.js)中,则只有该路由将使用它。如果您将中间件放置在 dogs.js 中 GET 路由之后,则只有 POST 路由将使用它(只要它以响应结束)。 - Nocturno
啊,我的错。我本意是要写 app.get('/dogs', dogs)(app),因为问题是关于 app.get 和 route.get 的 get 功能。你正在展示如何分离路由以使其易于管理。但如果我们谈论的是 app.get,我的轶事不是正确的吗?如果我能上电脑,我会编辑我的上面的评论。 - Ravenous
3
当我使用express.Router()或express()时,我的应用程序仍然可以正常工作,但我不理解它们之间的区别 :( - Ajay Suwalka
4
@Ajay Suwalka,我不知道如何进一步说明我已经说过的内容了。文档中说,“路由器对象是中间件和路由的隔离实例”。我也喜欢 @Jonathan Lonowski 上面的评论,“Router 不会独自 .listen() 请求。”这可能是主要区别。 - Nocturno
1
@Shashwat 我认为它们不相同。Router()被定义并导入到require('express')模块中。看起来,require('router')正在导入一个完全与Express无关的不同模块。 - Nocturno
显示剩余6条评论

36

Express 4.0新引入了Router。如该网站所述:

express.Router类可用于创建模块化可挂载路由处理程序。 Router实例是完整的中间件和路由系统;因此,它通常被称为“迷你应用程序”。

https://scotch.io/tutorials/learn-to-use-the-new-router-in-expressjs-4有一篇很好的文章,介绍了路由器的区别和可以使用路由器完成的操作。

总结一下:

使用路由器可以更轻松地使您的代码模块化。 您可以将路由器用作:

  1. 基本路由:主页,关于
  2. 用于将请求记录到控制台的路由中间件
  3. 带参数的路由
  4. 用于验证特定参数的路由中间件
  5. 验证传递给某个路由的参数

注意:

在Express 4中删除了app.router对象,在Express 5中重新引入。在新版本中,它只是对Express基础路由器的引用,不像在Express 3中,应用程序必须明确加载它。


15

它们的不同之处

每个人,包括文档,都倾向于回顾它们有多相似,但实际上没有提到任何差异。事实上,它们是不同的。

var bigApp = express();
var miniApp = express.Router();

listen()

最明显的区别在于bigApp将提供listen,这只是一种相当令人困惑的方式来完成本应该简单明了的httphttps模块中的操作:

var server = require('http').createServer(bigApp);

server.listen(8080, function () {
  console.info(server.address());  
});

我认为这是一种反模式,因为它抽象化和混淆掉了原本并不复杂或困难的东西,并且使得使用 Websockets 和其他需要原始 HTTP 服务器的中间件变得困难。

内部状态

最重要的区别在于,所有的bigApp都有独立的内部状态。

bigApp.enable('trust proxy');
bigApp.enabled('trust proxy');
// true

var bigApp2 = express();
bigApp2.enabled('trust proxy');
// false

bigApp.use('/bunnies', bigApp2);
// WRONG! '/bunnies' will NOT trust proxies

miniApp传递给bigApp,它将以保留其内部状态和this性质的方式由bigApp操作,并相应地处理这些路由。

bigApp.enable('trust proxy');
bigApp.enabled('trust proxy');
// true

var miniApp = express.Router();

bigApp.use('/bunnies', miniApp);
// CORRECT! All state and such are preserved

这很重要,因为expresshttp.ServerRequesthttpServerResponse对象做了许多(有时是棘手的)修改或劫持操作 - 例如修改(或劫持)req.urlreq.originalUrl以及其他一些你一直在使用但没有意识到的属性 - 而你可能不希望这些被复制和分离。

更小的API

Router可以使用更少、更明确定义的函数:

  • .use(mount, fn)
  • .all(mount, fn)
  • .options(mount, fn)
  • .head(mount, fn)
  • .get(mount, fn)
  • .post(mount, fn)
  • .patch(mount, fn)
  • .put(mount, fn)
  • .delete(mount, fn)
  • .route(mount).XXXX
  • .param(name, cb).XXXX

还有其他一些方便的方法,比如basic(),但你不会发现set()enable()或其他改变大型应用状态的方法。


7

假设您的应用程序有些复杂。因此,我们首先将应用程序分成多个模块,以便对一个模块的更改不会干扰其他模块,您可以继续对单个模块进行操作,但是最终需要将所有内容集成到一个模块中,因为您正在构建一个单一的应用程序。就像我们有一个主要的应用程序和几个子应用程序,其父级是主要应用程序。因此,在创建父应用程序时,我们使用以下方法之一:

const express = require("express");
const parent = express();

我们需要将子应用程序引入到这个父应用程序中。但由于子应用程序不是完全独立的应用程序(因为它们在同一上下文中运行 - Java术语),所以Express提供了通过Express的Router函数来完成的方法。这就是每个子模块文件中要做的事情,让我们称其中一个子模块为aboutme

const express = require("express");
export const router = express.Router();

通过 export 我们将这个模块提供给其他人使用,由于我们已经将其模块化,所以需要通过 node 的 require 函数将模块文件提供给父级应用程序,就像任何第三方模块一样。父级文件看起来是这样的:

const express = require("express");
const parent = express();
const child = require("./aboutme");

在我们将此子模块提供给父应用程序后,我们需要告诉父应用程序何时使用此子应用程序。举个例子,当用户访问路径aboutme时,我们需要子应用程序“关于我”来处理请求,并通过使用Express的use方法来实现:

parent.use("/aboutme", child);

一次性操作后,父文件看起来像这样:

const express = require("express");
const parent = express();
const child = require("./aboutme");

parent.use("/aboutme", child);

最重要的是,父进程可以启动服务器,而子进程则不能。希望这能够澄清问题。如果需要更多信息,您可以查看源代码,虽然这需要一些时间,但可以获得很多信息。

3
这句话的意思是:这个应该是parent.use('/aboutme', child)吧? - Kees de Kooter
但是孩子能做什么,父母不能做呢?为什么不同时将express()用于主应用程序和所有子级应用程序? - Ron Inbar

7
app.route('/book')
  .get(function (req, res) {
    res.send('Get a random book')
  })
  .post(function (req, res) {
    res.send('Post a random book')
  })

就像上面的例子一样,我们可以在一个路由下添加不同的HTTP请求方法。


4
楼主问的是 express.Router,而不是 app.route - Ron Inbar

3

express.Router 有多个选项:

  • 启用大小写敏感模式:默认情况下禁用此功能,使 /show 路由与 /Show 不同
  • 严格路由模式:默认情况下禁用此功能,使 /show/ 路由与 /show 不同
  • 我们可以为特定路由添加特定的中间件

3
使用app.js编写路由意味着它们对所有用户都是可访问的,因为在应用程序启动时加载了app.js。然而,将路由放入express.router()小应用程序中可以保护和限制其可访问性。

3
简而言之,与app.get()相比,express.Router可以做更多的事情,如中间件。此外,您可以使用express.Router()定义一个或多个路由器对象。

1
在测验中的一个问题中问到:"express.Router() 创建了一个行为类似于 app 对象的对象。"
正确答案是“真”。我知道我们可以使用其中任何一种来创建路由器,但是否可以说在所有情况下它们都不相同呢?如果我的理解正确,express() 变量可以做更多的事情,比如启动服务器,而另一个则不能。

这并不意味着它是无用的,假设您想制作一个使用自己API的应用程序。为API创建两个不同的服务器毫无意义。这可以通过路由有效地完成。 - random_hooman

-1
在一个复杂的应用程序中,app 是模块,例如文章和用户。路由器是模块中的控制器或操作,例如文章创建和列表。例如 URL https://example.com/article/create 解析文章模块并创建路由器。
此外,app 和路由器可以嵌套在不同的层级中。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接