路由
router 对象是中间件和路由的一个实例。你可以把它看作一个“迷你应用”,只能够执行中间件和路由功能。每个 Express 应用都有一个内置的应用路由。
🌐 A router object is an instance of middleware and routes. You can think of it
as a “mini-application,” capable only of performing middleware and routing
functions. Every Express application has a built-in app router.
路由本身的行为就像中间件,因此你可以将它用作 app.use() 的参数,或者用作另一个路由的 use() 方法的参数。
🌐 A router behaves like middleware itself, so you can use it as an argument to app.use() or as the argument to another router’s use() method.
顶层 express 对象具有一个 Router() 方法,用于创建一个新的 router 对象。
🌐 The top-level express object has a Router() method that creates a new router object.
一旦你创建了一个路由对象,你可以像对待应用一样向它添加中间件和 HTTP 方法路由(例如 get、put、post 等)。例如:
🌐 Once you’ve created a router object, you can add middleware and HTTP method routes (such as get, put, post,
and so on) to it just like an application. For example:
// invoked for any requests passed to this routerrouter.use((req, res, next) => { // .. some logic here .. like any other middleware next();});
// will handle any request that ends in /events// depends on where the router is "use()'d"router.get('/events', (req, res, next) => { // ..});import { type Request, type Response, type NextFunction } from 'express';
// invoked for any requests passed to this routerrouter.use((req: Request, res: Response, next: NextFunction) => { // .. some logic here .. like any other middleware next();});
// will handle any request that ends in /events// depends on where the router is "use()'d"router.get('/events', (req: Request, res: Response, next: NextFunction) => { // ..});然后你可以用这种方式为特定的根 URL 使用路由,将你的路由分到不同的文件甚至小型应用中。
🌐 You can then use a router for a particular root URL in this way separating your routes into files or even mini-apps.
// only requests to /calendar/* will be sent to our "router"app.use('/calendar', router);方法
🌐 Methods
Router()
Arguments
options配置路由行为的选项。
caseSensitive启用区分大小写。默认禁用,将 /Foo 和 /foo 视为相同。
mergeParams保留来自父路由的 req.params 值。如果父路由和子路由具有冲突的参数名称,以子路由的值为准。
strict启用严格路由。默认禁用,路由将 /foo 和 /foo/ 视为相同。
创建一个新的路由对象。
🌐 Creates a new router object.
const express = require('express');const router = express.Router({ caseSensitive: true, strict: true });
router.get('/events', (req, res) => { res.send('events');});
// mount the router on an appapp.use('/calendar', router);import express from 'express';
const router = express.Router({ caseSensitive: true, strict: true });
router.get('/events', (req, res) => { res.send('events');});
// mount the router on an appapp.use('/calendar', router);import express, { type Request, type Response } from 'express';
const router = express.Router({ caseSensitive: true, strict: true });
router.get('/events', (req: Request, res: Response) => { res.send('events');});
// mount the router on an appapp.use('/calendar', router);router.all()
Arguments
path调用路由回调的路径。
callback一个或多个中间件/处理函数。每个函数都可以调用 next('route') 来跳过剩余的回调。
这个方法就像 router.METHOD() 方法,只不过它匹配所有 HTTP 方法(动词)。
🌐 This method is just like the router.METHOD() methods, except that it matches all HTTP methods (verbs).
这种方法对于映射特定路径前缀或任意匹配的“全局”逻辑非常有用。例如,如果你将以下路由放在所有其他路由定义的顶部,它将要求从该点开始的所有路由都需要认证,并自动加载用户。请记住,这些回调不必作为终点;loadUser 可以执行一个任务,然后调用 next() 来继续匹配后续的路由。
🌐 This method is extremely useful for
mapping “global” logic for specific path prefixes or arbitrary matches.
For example, if you placed the following route at the top of all other
route definitions, it would require that all routes from that point on
would require authentication, and automatically load a user. Keep in mind
that these callbacks do not have to act as end points; loadUser
can perform a task, then call next() to continue matching subsequent
routes.
router.all('{*splat}', requireAuthentication, loadUser);或者等同于:
🌐 Or the equivalent:
router.all('{*splat}', requireAuthentication);router.all('{*splat}', loadUser);另一个例子是白名单的“全局”功能。在这里,例子与之前非常相似,但它仅限制以“/api”开头的路径:
🌐 Another example of this is white-listed “global” functionality. Here, the example is much like before, but it only restricts paths prefixed with “/api”:
router.all('/api/{*splat}', requireAuthentication);router.METHOD()
Arguments
path调用路由回调的路径。
callback一个或多个中间件/处理函数。每个函数都可以调用 next('route') 来跳过剩余的回调。
router.METHOD() 方法在 Express 中提供路由功能,其中 METHOD 是 HTTP 方法之一,例如 GET、PUT、POST 等,使用小写字母。因此,实际的方法是 router.get()、router.post()、router.put(),依此类推。
🌐 The router.METHOD() methods provide the routing functionality in Express,
where METHOD is one of the HTTP methods, such as GET, PUT, POST, and so on,
in lowercase. Thus, the actual methods are router.get(), router.post(),
router.put(), and so on.
Note
router.get() 函数会自动被调用用于 HTTP HEAD 方法,此外,如果 router.head() 在 router.get() 之前没有被调用过该路径,也会用于 GET 方法。
🌐 The router.get() function is automatically called for the HTTP HEAD method in addition to the
GET method if router.head() was not called for the path before router.get().
Note
router.query() 路由 HTTP QUERY 请求,类似于 app.query()。QUERY 方法受运行时控制,并且需要 Node.js >=20.19.3 <21 || >=22.2.0。
你可以提供多个回调,所有回调都会被平等对待,并且行为就像中间件,只是这些回调可以调用 next('route') 来跳过剩余的路由回调。你可以使用这个机制在路由上执行预条件检查,然后在没有必要继续匹配该路由时将控制权传递给后续的路由。
🌐 You can provide multiple callbacks, and all are treated equally, and behave just
like middleware, except that these callbacks may invoke next('route')
to bypass the remaining route callback(s). You can use this mechanism to perform
pre-conditions on a route then pass control to subsequent routes when there is no
reason to proceed with the route matched.
下面的代码片段展示了最简单的路由定义。Express 会将路径字符串转换为正则表达式,用于内部匹配传入的请求。执行这些匹配时不会考虑查询字符串,例如 “GET /” 会匹配以下路由,“GET /?name=tobi” 也会匹配。
🌐 The following snippet illustrates the most simple route definition possible. Express translates the path strings to regular expressions, used internally to match incoming requests. Query strings are not considered when performing these matches, for example “GET /” would match the following route, as would “GET /?name=tobi”.
router.get('/', (req, res) => { res.send('hello world');});import { type Request, type Response } from 'express';
router.get('/', (req: Request, res: Response) => { res.send('hello world');});你也可以使用正则表达式——如果你有非常具体的限制,这会很有用,例如下面的正则表达式可以匹配 “GET /commits/71dbb9c” 以及 “GET /commits/71dbb9c..4c084f9”。
🌐 You can also use regular expressions—useful if you have very specific constraints, for example the following would match “GET /commits/71dbb9c” as well as “GET /commits/71dbb9c..4c084f9”.
router.get(/^\/commits\/(\w+)(?:\.\.(\w+))?$/, (req, res) => { const from = req.params[0]; const to = req.params[1] || 'HEAD'; res.send(`commit range ${from}..${to}`);});import { type Request, type Response } from 'express';
router.get(/^\/commits\/(\w+)(?:\.\.(\w+))?$/, (req: Request, res: Response) => { const from = req.params[0]; const to = req.params[1] || 'HEAD'; res.send(`commit range ${from}..${to}`);});你可以使用 next 原语在不同的中间件函数之间实现流控制,基于特定的程序状态。调用带有字符串 'router' 的 next 将导致该路由上所有剩余的路由回调被跳过。
🌐 You can use next primitive to implement a flow control between different
middleware functions, based on a specific program state. Invoking next with
the string 'router' will cause all the remaining route callbacks on that router
to be bypassed.
以下示例说明了 next('router') 的用法。
🌐 The following example illustrates next('router') usage.
function fn(req, res, next) { console.log('I come here'); next('router');}router.get('/foo', fn, (req, res, next) => { console.log("I don't come here");});router.get('/foo', (req, res, next) => { console.log("I don't come here");});app.get('/foo', (req, res) => { console.log(' I come here too'); res.end('good');});import { type Request, type Response, type NextFunction } from 'express';
function fn(req: Request, res: Response, next: NextFunction) { console.log('I come here'); next('router');}router.get('/foo', fn, (req: Request, res: Response, next: NextFunction) => { console.log("I don't come here");});router.get('/foo', (req: Request, res: Response, next: NextFunction) => { console.log("I don't come here");});app.get('/foo', (req: Request, res: Response) => { console.log(' I come here too'); res.end('good');});router.param()
Arguments
name要附加触发器的路由参数名称。与 app.param() 不同,它不接受数组。
callback作为 callback(req, res, next, value, name) 调用的触发器。
向路由参数添加回调触发器,其中 name 是参数的名称,callback 是回调函数。name 参数是必需的。
🌐 Adds callback triggers to route parameters, where name is the name of the parameter and callback is the callback function. The name parameter is required.
回调函数的参数是:
🌐 The parameters of the callback function are:
req,请求对象。res,响应对象。next,表示下一个中间件函数。name参数的值。- 参数的名称。
Note
与 app.param() 不同,router.param() 不接受路由参数数组。
🌐 Unlike app.param(), router.param() does not accept an array of route parameters.
例如,当 :user 出现在路由路径中时,你可以将用户加载逻辑映射,以自动向路由提供 req.user,或对参数输入执行验证。
🌐 For example, when :user is present in a route path, you may map user loading logic to automatically provide req.user to the route, or perform validations on the parameter input.
router.param('user', (req, res, next, id) => { // try to get the user details from the User model and attach it to the request object User.find(id, (err, user) => { if (err) { next(err); } else if (user) { req.user = user; next(); } else { next(new Error('failed to load user')); } });});import { type Request, type Response, type NextFunction } from 'express';
router.param('user', (req: Request, res: Response, next: NextFunction, id: string) => { // try to get the user details from the User model and attach it to the request object User.find(id, (err, user) => { if (err) { next(err); } else if (user) { req.user = user; next(); } else { next(new Error('failed to load user')); } });});参数回调函数是定义它们的路由的本地函数。它们不会被挂载的应用或路由继承,也不会对从父路由继承的路由参数触发。因此,在 router 上定义的参数回调函数只会被 router 路由上定义的路由参数触发。
🌐 Param callback functions are local to the router on which they are defined. They are not inherited by mounted apps or routers, nor are they triggered for route parameters inherited from parent routers. Hence, param callbacks defined on router will be triggered only by route parameters defined on router routes.
参数回调在一次请求-响应周期中只会被调用一次,即使该参数在多个路由中匹配,如以下示例所示。
🌐 A param callback will be called only once in a request-response cycle, even if the parameter is matched in multiple routes, as shown in the following examples.
router.param('id', (req, res, next, id) => { console.log('CALLED ONLY ONCE'); next();});
router.get('/user/:id', (req, res, next) => { console.log('although this matches'); next();});
router.get('/user/:id', (req, res) => { console.log('and this matches too'); res.end();});import { type Request, type Response, type NextFunction } from 'express';
router.param('id', (req: Request, res: Response, next: NextFunction, id: string) => { console.log('CALLED ONLY ONCE'); next();});
router.get('/user/:id', (req: Request, res: Response, next: NextFunction) => { console.log('although this matches'); next();});
router.get('/user/:id', (req: Request, res: Response) => { console.log('and this matches too'); res.end();});在 GET /user/42 上,打印如下:
🌐 On GET /user/42, the following is printed:
CALLED ONLY ONCEalthough this matchesand this matches toorouter.route()
Arguments
path创建路由的路径。
返回一个单一路由的实例,你可以使用它来处理带有可选中间件的 HTTP 动词。使用 router.route() 可以避免重复的路由命名,从而避免输入错误。
🌐 Returns an instance of a single route which you can then use to handle HTTP verbs
with optional middleware. Use router.route() to avoid duplicate route naming and
thus typing errors.
在上面的 router.param() 示例的基础上,以下代码展示了如何使用 router.route() 来指定各种 HTTP 方法处理器。
🌐 Building on the router.param() example above, the following code shows how to use
router.route() to specify various HTTP method handlers.
const router = express.Router();
router.param('user_id', (req, res, next, id) => { // sample user, would actually fetch from DB, etc... req.user = { id, name: 'TJ', }; next();});
router .route('/users/:user_id') .all((req, res, next) => { // runs for all HTTP verbs first // think of it as route specific middleware! next(); }) .get((req, res, next) => { res.json(req.user); }) .put((req, res, next) => { // just an example of maybe updating the user req.user.name = req.params.name; // save user ... etc res.json(req.user); }) .post((req, res, next) => { next(new Error('not implemented')); }) .delete((req, res, next) => { next(new Error('not implemented')); });import express, { type Request, type Response, type NextFunction } from 'express';
const router = express.Router();
router.param('user_id', (req: Request, res: Response, next: NextFunction, id: string) => { // sample user, would actually fetch from DB, etc... req.user = { id, name: 'TJ', }; next();});
router .route('/users/:user_id') .all((req: Request, res: Response, next: NextFunction) => { // runs for all HTTP verbs first // think of it as route specific middleware! next(); }) .get((req: Request, res: Response, next: NextFunction) => { res.json(req.user); }) .put((req: Request, res: Response, next: NextFunction) => { // just an example of maybe updating the user req.user.name = req.params.name; // save user ... etc res.json(req.user); }) .post((req: Request, res: Response, next: NextFunction) => { next(new Error('not implemented')); }) .delete((req: Request, res: Response, next: NextFunction) => { next(new Error('not implemented')); });这种方法重用单个 /users/:user_id 路径,并为各种 HTTP 方法添加处理程序。
🌐 This approach re-uses the single /users/:user_id path and adds handlers for
various HTTP methods.
Note
当你使用 router.route() 时,中间件的排序是基于 路由 被创建的时间,而不是方法处理程序被添加到路由的时间。为此,你可以将方法处理程序视为属于它们被添加的路由。
🌐 When you use router.route(), middleware ordering is based on when the route is created, not
when method handlers are added to the route. For this purpose, you can consider method handlers to
belong to the route to which they were added.
router.use()
Arguments
path中间件的挂载路径。默认值为 /。
callback一个或多个要挂载的中间件函数。
使用指定的中间件函数或函数组合,可选的挂载路径 path,默认为 ”/”。
🌐 Uses the specified middleware function or functions, with optional mount path path, that defaults to ”/”.
这种方法类似于 app.use()。下面描述了一个简单的示例和使用案例。有关更多信息,请参见 app.use()。
🌐 This method is similar to app.use(). A simple example and use case is described below. See app.use() for more information.
中间件就像管道:请求从定义的第一个中间件函数开始,然后沿着中间件堆栈“向下”处理每个匹配的路径。
🌐 Middleware is like a plumbing pipe: requests start at the first middleware function defined and work their way “down” the middleware stack processing for each path they match.
const express = require('express');const app = express();const router = express.Router();
// simple logger for this router's requests// all requests to this router will first hit this middlewarerouter.use((req, res, next) => { console.log('%s %s %s', req.method, req.url, req.path); next();});
// this will only be invoked if the path starts with /bar from the mount pointrouter.use('/bar', (req, res, next) => { // ... maybe some additional /bar logging ... next();});
// always invokedrouter.use((req, res, next) => { res.send('Hello World');});
app.use('/foo', router);
app.listen(3000);import express from 'express';
const app = express();const router = express.Router();
// simple logger for this router's requests// all requests to this router will first hit this middlewarerouter.use((req, res, next) => { console.log('%s %s %s', req.method, req.url, req.path); next();});
// this will only be invoked if the path starts with /bar from the mount pointrouter.use('/bar', (req, res, next) => { // ... maybe some additional /bar logging ... next();});
// always invokedrouter.use((req, res, next) => { res.send('Hello World');});
app.use('/foo', router);
app.listen(3000);import express, { type Request, type Response, type NextFunction } from 'express';
const app = express();const router = express.Router();
// simple logger for this router's requests// all requests to this router will first hit this middlewarerouter.use((req: Request, res: Response, next: NextFunction) => { console.log('%s %s %s', req.method, req.url, req.path); next();});
// this will only be invoked if the path starts with /bar from the mount pointrouter.use('/bar', (req: Request, res: Response, next: NextFunction) => { // ... maybe some additional /bar logging ... next();});
// always invokedrouter.use((req: Request, res: Response, next: NextFunction) => { res.send('Hello World');});
app.use('/foo', router);
app.listen(3000);“mount” 路径会被去掉,并且对中间件函数不可见。这个特性的主要作用是,无论其“前缀”路径名如何,被挂载的中间件函数都可以在不更改代码的情况下运行。
🌐 The “mount” path is stripped and is not visible to the middleware function. The main effect of this feature is that a mounted middleware function may operate without code changes regardless of its “prefix” pathname.
使用 router.use() 定义中间件的顺序非常重要。它们会按顺序被调用,因此顺序决定了中间件的优先级。例如,通常日志记录器是你会使用的第一个中间件,以确保每个请求都被记录。
🌐 The order in which you define middleware with router.use() is very important.
They are invoked sequentially, thus the order defines middleware precedence. For example,
usually a logger is the very first middleware you would use, so that every request gets logged.
const logger = require('morgan');
router.use(logger());router.use(express.static(path.join(__dirname, 'public')));router.use((req, res) => { res.send('Hello');});import logger from 'morgan';
router.use(logger());router.use(express.static(path.join(__dirname, 'public')));router.use((req, res) => { res.send('Hello');});import logger from 'morgan';import { type Request, type Response } from 'express';
router.use(logger());router.use(express.static(path.join(__dirname, 'public')));router.use((req: Request, res: Response) => { res.send('Hello');});现在假设你想忽略对静态文件的日志请求,但继续记录 logger() 之后定义的路由和中间件。你只需将对 express.static() 的调用移动到顶部,在添加日志中间件之前:
🌐 Now suppose you wanted to ignore logging requests for static files, but to continue
logging routes and middleware defined after logger(). You would simply move the call to express.static() to the top,
before adding the logger middleware:
router.use(express.static(path.join(__dirname, 'public')));router.use(logger());router.use((req, res) => { res.send('Hello');});import { type Request, type Response } from 'express';
router.use(express.static(path.join(__dirname, 'public')));router.use(logger());router.use((req: Request, res: Response) => { res.send('Hello');});另一个例子是从多个目录提供文件,优先使用 ”./public” 而不是其他目录:
🌐 Another example is serving files from multiple directories, giving precedence to ”./public” over the others:
app.use(express.static(path.join(__dirname, 'public')));app.use(express.static(path.join(__dirname, 'files')));app.use(express.static(path.join(__dirname, 'uploads')));router.use() 方法还支持命名参数,这样你为其他路由设置的挂载点就可以利用命名参数进行预加载。
🌐 The router.use() method also supports named parameters so that your mount points
for other routers can benefit from preloading using named parameters.
Note
虽然这些中间件函数是通过特定的路由添加的,但它们的运行时间是由它们附加的路径决定的(而不是路由)。因此,通过一个路由添加的中间件如果其路由匹配,可能会在其他路由上运行。
🌐 Although these middleware functions are added via a particular router, when they run is defined by the path they are attached to (not the router). Therefore, middleware added via one router may run for other routers if its routes match.
例如,这段代码展示了在同一路径上挂载两个不同的路由:
🌐 For example, this code shows two different routers mounted on the same path:
const authRouter = express.Router();const openRouter = express.Router();
authRouter.use(require('./authenticate').basic(usersdb));
authRouter.get('/:user_id/edit', (req, res, next) => { // ... Edit user UI ...});openRouter.get('/', (req, res, next) => { // ... List users ...});openRouter.get('/:user_id', (req, res, next) => { // ... View user ...});
app.use('/users', authRouter);app.use('/users', openRouter);import express, { type Request, type Response, type NextFunction } from 'express';import { basic } from './authenticate';
const authRouter = express.Router();const openRouter = express.Router();
authRouter.use(basic(usersdb));
authRouter.get('/:user_id/edit', (req: Request, res: Response, next: NextFunction) => { // ... Edit user UI ...});openRouter.get('/', (req: Request, res: Response, next: NextFunction) => { // ... List users ...});openRouter.get('/:user_id', (req: Request, res: Response, next: NextFunction) => { // ... View user ...});
app.use('/users', authRouter);app.use('/users', openRouter);尽管通过 authRouter 添加了身份验证中间件,它仍会在 openRouter 定义的路由上运行,因为两个路由都挂载在 /users 上。为了避免这种行为,为每个路由使用不同的路径。
🌐 Even though the authentication middleware was added via the authRouter it will run on the routes defined by the openRouter as well since both routers were mounted on /users. To avoid this behavior, use different paths for each router.