🌐 Nodejs.cn

路由

路由 指的是应用的端点(URI)如何响应客户端请求。
有关路由的介绍,请参见 基本路由

🌐 Routing refers to how an application’s endpoints (URIs) respond to client requests. For an introduction to routing, see Basic routing.

你可以使用 Express app 对象的方法来定义路由,这些方法对应于 HTTP 方法;例如,使用 app.get() 来处理 GET 请求,使用 app.post 来处理 POST 请求。完整列表请参见 app.METHOD。你也可以使用 app.all() 来处理所有 HTTP 方法,或使用 app.use() 指定中间件作为回调函数(详情请参见 使用中间件)。

🌐 You define routing using methods of the Express app object that correspond to HTTP methods; for example, app.get() to handle GET requests and app.post to handle POST requests. For a full list, see app.METHOD. You can also use app.all() to handle all HTTP methods and app.use() to specify middleware as the callback function (See Using middleware for details).

这些路由方法指定了一个回调函数(有时称为“处理函数”),当应用收到与指定路由(端点)和 HTTP 方法匹配的请求时,Express 会自动运行该函数。换句话说,应用“监听”与指定路由和方法匹配的请求,当检测到匹配时,它会调用指定的回调函数。

🌐 These routing methods specify a callback function (sometimes called a “handler function”) that Express automatically runs when the application receives a request matching the specified route (endpoint) and HTTP method. In other words, the application “listens” for requests that match the specified route(s) and method(s), and when it detects a match, it calls the specified callback function.

实际上,路由方法可以将多个回调函数作为参数。使用多个回调函数时,重要的是将 next 作为回调函数的参数提供,然后在函数体内调用 next() 来将控制权移交给下一个回调函数。

🌐 In fact, the routing methods can have more than one callback function as arguments. With multiple callback functions, it is important to provide next as an argument to the callback function and then call next() within the body of the function to hand off control to the next callback.

以下代码是一个非常基础的路由示例。

🌐 The following code is an example of a very basic route.

index.cjs
const express = require('express');
const app = express();
// respond with "hello world" when a GET request is made to the homepage
app.get('/', (req, res) => {
res.send('hello world');
});

路由方法

🌐 Route methods

路由方法是从 HTTP 方法之一派生的,并附加到 express 类的一个实例上。

🌐 A route method is derived from one of the HTTP methods, and is attached to an instance of the express class.

以下代码是为应用根路径定义的 GETPOST 方法的路由示例。

🌐 The following code is an example of routes that are defined for the GET and the POST methods to the root of the app.

// GET method route
app.get('/', (req, res) => {
res.send('GET request to the homepage');
});
// POST method route
app.post('/', (req, res) => {
res.send('POST request to the homepage');
});

Express 支持与所有 HTTP 请求方法对应的方法:getpost,等等。完整列表请参见 app.METHOD

🌐 Express supports methods that correspond to all HTTP request methods: get, post, and so on. For a full list, see app.METHOD.

有一种特殊的路由方法,app.all(),用于在某个路径上为所有 HTTP 请求方法加载中间件函数。例如,以下处理程序会在对路由 "/secret" 的请求中执行,无论使用 GETQUERYPOSTPUTDELETE,还是 http 模块 支持的任何其他 HTTP 请求方法。

🌐 There is a special routing method, app.all(), used to load middleware functions at a path for all HTTP request methods. For example, the following handler is executed for requests to the route "/secret" whether using GET, QUERY, POST, PUT, DELETE, or any other HTTP request method supported in the http module.

app.all('/secret', (req, res, next) => {
console.log('Accessing the secret section ...');
next(); // pass control to the next handler
});

路由路径

🌐 Route paths

路由路径结合请求方法,定义了可以发起请求的端点。路由路径可以是字符串或正则表达式。

🌐 Route paths, in combination with a request method, define the endpoints at which requests can be made. Route paths can be strings or regular expressions.

Note

Express 使用 path-to-regexp v8 来匹配路由路径;有关定义路由路径的所有可能性,请参阅 path-to-regexp 文档。 Express Playground Router 是一个测试基本 Express 路由的便捷工具,尽管它不支持模式匹配。

🌐 Express uses path-to-regexp v8 for matching the route paths; see the path-to-regexp documentation for all the possibilities in defining route paths. Express Playground Router is a handy tool for testing basic Express routes, although it does not support pattern matching.

字符串路径

🌐 String paths

字符串路径与请求完全匹配。点(.)和连字符(-)按字面解释。

🌐 String paths match requests exactly. The dot (.) and hyphen (-) are interpreted literally.

Warning

查询字符串不是路由路径的一部分。

🌐 Query strings are not part of the route path.

app.get('/', (req, res) => {
res.send('root');
});
app.get('/about', (req, res) => {
res.send('about');
});
app.get('/random.text', (req, res) => {
res.send('random.text');
});

通配符

🌐 Wildcards

通配符匹配前缀之后的任何路径。它们必须有名称,就像路由参数一样,并且作为路径段数组被捕获。

🌐 Wildcards match any path after a prefix. They must have a name, just like route parameters, and are captured as arrays of path segments.

app.get('/files/*filepath', (req, res) => {
// GET /files/images/logo.png
console.dir(req.params.filepath);
// => [ 'images', 'logo.png' ]
res.send(`File: ${req.params.filepath.join('/')}`);
});

要同时匹配根路径,请将通配符用大括号括起来:

🌐 To also match the root path, wrap the wildcard in braces:

// Matches / , /foo , /foo/bar , etc.
app.get('/{*splat}', (req, res) => {
// GET / => req.params.splat = []
// GET /foo/bar => req.params.splat = [ 'foo', 'bar' ]
res.send('ok');
});

可选段

🌐 Optional segments

使用大括号来定义路由路径中的可选片段。当该片段不存在时,参数会从 req.params 中省略。

🌐 Use braces to define optional segments in a route path. When the segment is not present, the parameter is omitted from req.params.

app.get('/:file{.:ext}', (req, res) => {
// GET /image.png => req.params = { file: 'image', ext: 'png' }
// GET /image => req.params = { file: 'image' }
res.send('ok');
});

Caution

字符 ?+*[]() 是保留的,不能在路由路径中作为文字字符使用。如有需要,请使用 \ 对它们进行转义。

🌐 The characters ?, +, *, [], and () are reserved and cannot be used as literal characters in route paths. Use \ to escape them if needed.

正则表达式

🌐 Regular expressions

你也可以将正则表达式用作路由路径。当你需要更复杂的匹配逻辑时,这非常有用。

🌐 You can also use regular expressions as route paths. This is useful when you need more complex matching logic.

// Matches any path containing "a"
app.get(/a/, (req, res) => {
res.send('/a/');
});
// Matches paths ending with "fly" (butterfly, dragonfly, etc.)
app.get(/.*fly$/, (req, res) => {
res.send('/.*fly$/');
});

路由参数

🌐 Route parameters

路由参数是命名的 URL 段,用于捕获在 URL 中其位置上指定的值。捕获的值会填充在 req.params 对象中,路由参数在路径中指定的名称作为它们各自的键。

🌐 Route parameters are named URL segments that are used to capture the values specified at their position in the URL. The captured values are populated in the req.params object, with the name of the route parameter specified in the path as their respective keys.

Route path: /users/:userId/books/:bookId
Request URL: http://localhost:3000/users/34/books/8989
req.params: { "userId": "34", "bookId": "8989" }

要定义带有路由参数的路由,只需像下面所示在路由的路径中指定路由参数。

🌐 To define routes with route parameters, simply specify the route parameters in the path of the route as shown below.

app.get('/users/:userId/books/:bookId', (req, res) => {
res.send(req.params);
});

在 TypeScript 中,@types/express 会从路由路径推断参数,所以在上面的处理函数中,req.params.userIdreq.params.bookId 已经被类型化为 string,无需额外注解。读取路由中不存在的名称(例如 req.params.other)会导致类型错误。只有在处理函数与路由分开定义时才需要对参数进行注解,因为类型检查器无法再看到路径。在这种情况下,将它们作为 Request 的第一个类型参数传入:

🌐 In TypeScript, @types/express infers the parameters from the route path, so in the handler above req.params.userId and req.params.bookId are already typed as string with no extra annotation. Reading a name that is not in the route (such as req.params.other) is a type error. You only need to annotate the parameters when the handler is defined separately from the route, because the type checker can no longer see the path. In that case, pass them as the first type argument of Request:

import { type Request, type Response } from 'express';
const sendParams = (req: Request<{ userId: string; bookId: string }>, res: Response) => {
res.send(req.params);
};
app.get('/users/:userId/books/:bookId', sendParams);

Caution

路由参数的名称必须由“字母数字字符”([A-Za-z0-9_])组成。

🌐 The name of route parameters must be made up of “word characters” ([A-Za-z0-9_]).

由于连字符(-)和点(.)被字面解释,它们可以与路由参数一起用于有用的目的。

🌐 Since the hyphen (-) and the dot (.) are interpreted literally, they can be used along with route parameters for useful purposes.

Route path: /flights/:from-:to
Request URL: http://localhost:3000/flights/LAX-SFO
req.params: { "from": "LAX", "to": "SFO" }
Route path: /plantae/:genus.:species
Request URL: http://localhost:3000/plantae/Prunus.persica
req.params: { "genus": "Prunus", "species": "persica" }

Caution

路由路径不支持正则表达式字符。请改为使用路径数组或正则表达式。更多信息请参见 路径路由匹配语法

🌐 Regexp characters are not supported in route paths. Use an array of paths or regular expressions instead. See the path route matching syntax for more information.

路由处理器

🌐 Route handlers

你可以提供多个回调函数,这些回调函数的行为类似于 middleware 来处理请求。唯一的例外是这些回调可能会调用 next('route') 来跳过剩余的路由回调。你可以使用这个机制对路由施加先决条件,如果没有理由继续当前路由,则将控制权传递给后续路由。

🌐 You can provide multiple callback functions that behave like middleware to handle a request. The only exception is that these callbacks might invoke next('route') to bypass the remaining route callbacks. You can use this mechanism to impose pre-conditions on a route, then pass control to subsequent routes if there’s no reason to proceed with the current route.

app.get('/user/:id', (req, res, next) => {
if (req.params.id === '0') {
return next('route');
}
res.send(`User ${req.params.id}`);
});
app.get('/user/:id', (req, res) => {
res.send('Special handler for user ID 0');
});

在这个例子中:

🌐 In this example:

  • GET /user/5 → 由第一条路由处理 → 发送 “用户 5”
  • GET /user/0 → 第一个路由调用 next('route'),跳到下一个匹配的 /user/:id 路由

路由处理程序可以采用函数、函数数组或两者的组合形式,如以下示例所示。

🌐 Route handlers can be in the form of a function, an array of functions, or combinations of both, as shown in the following examples.

一个回调函数可以处理一个路由。例如:

🌐 A single callback function can handle a route. For example:

app.get('/example/a', (req, res) => {
res.send('Hello from A!');
});

一个路由可以由多个回调函数处理(确保你指定了 next 对象)。例如:

🌐 More than one callback function can handle a route (make sure you specify the next object). For example:

app.get(
'/example/b',
(req, res, next) => {
console.log('the response will be sent by the next function ...');
next();
},
(req, res) => {
res.send('Hello from B!');
}
);

一组回调函数可以处理一个路由。例如:

🌐 An array of callback functions can handle a route. For example:

const cb0 = function (req, res, next) {
console.log('CB0');
next();
};
const cb1 = function (req, res, next) {
console.log('CB1');
next();
};
const cb2 = function (req, res) {
res.send('Hello from C!');
};
app.get('/example/c', [cb0, cb1, cb2]);

独立函数和函数数组的组合可以处理一个路由。例如:

🌐 A combination of independent functions and arrays of functions can handle a route. For example:

const cb0 = function (req, res, next) {
console.log('CB0');
next();
};
const cb1 = function (req, res, next) {
console.log('CB1');
next();
};
app.get(
'/example/d',
[cb0, cb1],
(req, res, next) => {
console.log('the response will be sent by the next function ...');
next();
},
(req, res) => {
res.send('Hello from D!');
}
);

响应方法

🌐 Response methods

在下表中,响应对象(res)的方法可以向客户端发送响应,并终止请求-响应周期。如果在路由处理程序中未调用这些方法中的任何一个,客户端请求将会无限挂起。

🌐 The methods on the response object (res) in the following table can send a response to the client, and terminate the request-response cycle. If none of these methods are called from a route handler, the client request will be left hanging.

方法描述
res.download()提示下载文件
res.end()结束响应过程
res.json()发送 JSON 响应
res.jsonp()发送带 JSONP 支持的 JSON 响应
res.redirect()重定向请求
res.render()渲染视图模板
res.send()发送各种类型的响应
res.sendFile()以八位字节流发送文件
res.sendStatus()设置响应状态码并将其字符串表示作为响应主体发送

app.route()

你可以使用 app.route() 为一个路由路径创建可链式的路由处理程序。由于路径只在一个位置指定,创建模块化路由是有帮助的,同时也可以减少重复和拼写错误。有关路由的更多信息,请参阅:Router() 文档

🌐 You can create chainable route handlers for a route path by using app.route(). Because the path is specified at a single location, creating modular routes is helpful, as is reducing redundancy and typos. For more information about routes, see: Router() documentation.

这里是一个使用 app.route() 定义的链式路由处理器的示例。

🌐 Here is an example of chained route handlers that are defined by using app.route().

app
.route('/book')
.get((req, res) => {
res.send('Get a random book');
})
.post((req, res) => {
res.send('Add a book');
})
.put((req, res) => {
res.send('Update the book');
});

express.Router

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

🌐 Use the express.Router class to create modular, mountable route handlers. A Router instance is a complete middleware and routing system; for this reason, it is often referred to as a “mini-app”.

以下示例创建了一个作为模块的路由,在其中加载了一个中间件函数,定义了一些路由,并将路由模块挂载到主应用的路径上。

🌐 The following example creates a router as a module, loads a middleware function in it, defines some routes, and mounts the router module on a path in the main app.

在应用目录中创建名为 birds.js 的路由文件,内容如下:

🌐 Create a router file named birds.js in the app directory, with the following content:

birds.cjs
const express = require('express');
const router = express.Router();
// middleware that is specific to this router
const timeLog = (req, res, next) => {
console.log('Time: ', Date.now());
next();
};
router.use(timeLog);
// define the home page route
router.get('/', (req, res) => {
res.send('Birds home page');
});
// define the about route
router.get('/about', (req, res) => {
res.send('About birds');
});
module.exports = router;

然后,在应用中加载路由模块:

🌐 Then, load the router module in the app:

index.cjs
const birds = require('./birds');
// ...
app.use('/birds', birds);

该应用现在将能够处理对 /birds/birds/about 的请求,并调用特定于该路由的 timeLog 中间件函数。

🌐 The app will now be able to handle requests to /birds and /birds/about, as well as call the timeLog middleware function that is specific to the route.

但是如果父路由 /birds 有路径参数,子路由默认情况下无法访问它。要使其可访问,你需要将 mergeParams 选项传递给 Router 构造函数 参考

🌐 But if the parent route /birds has path parameters, it will not be accessible by default from the sub-routes. To make it accessible, you will need to pass the mergeParams option to the Router constructor reference.

const router = express.Router({ mergeParams: true });