aboutsummaryrefslogtreecommitdiff
path: root/files/zh-cn/learn/server-side/express_nodejs/routes/index.html
blob: eca1531fdeb565d5ea0e14255e500e7dbaeadfb6 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
---
title: Express 教程 4: 路由和控制器
slug: learn/Server-side/Express_Nodejs/routes
tags:
  - Express
  - Node
  - nodejs
  - 初学者
  - 服务器端编程
  - 路由
translation_of: Learn/Server-side/Express_Nodejs/routes
---
<div>{{LearnSidebar}}</div>

<div>{{PreviousMenuNext("Learn/Server-side/Express_Nodejs/mongoose", "Learn/Server-side/Express_Nodejs/Displaying_data", "Learn/Server-side/Express_Nodejs")}}</div>

<p class="summary">本节将为 <a href="https://developer.mozilla.org/zh-CN/docs/Learn/Server-side/Express_Nodejs/Tutorial_local_library_website">LocalLibrary</a> 站点中所需的资源端点(Endpoint)配置路由。先用空的处理函数搭建起路由处理的模块结构(下节会将它们扩充为真实的处理函数)。并详细介绍了 Express 路由模块的创建方法。</p>

<table class="learn-box standard-table">
 <tbody>
  <tr>
   <th scope="row">预备知识:</th>
   <td>回顾 <a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Express_Nodejs/Introduction">Express/Node 入门</a>。 完成本教程之前小节(<a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Express_Nodejs/mongoose">Express 教程 3:使用数据库 (Mongoose)</a> 等)。了解服务器端编程,了解正则表达式。</td>
  </tr>
  <tr>
   <th scope="row">学习目标:</th>
   <td>理解简单路由的创建方法。设置示例中所有 URL 端点。</td>
  </tr>
 </tbody>
</table>

<h2 id="概览">概览</h2>

<p><a href="https://developer.mozilla.org/zh-CN/docs/Learn/Server-side/Express_Nodejs/mongoose">上节</a> 定义了与数据库交互的 Mongoose 模型,使用一个(独立)脚本创建了一些初始记录。现在可以编写代码来向用户展示这些信息。首先要确定页面中应显示哪些信息,然后定义适当的 URL 来返回这些资源。随后应创建路由(URL 处理器)和视图(模板)来显示这些页面。</p>

<p>下图展示了 HTTP 请求/响应处理的主数据流和需要实现的行为。图中除视图(View)和路由(Route)外,还展示了控制器(Controller),它们是实际的请求处理函数,与路由请求代码是分开的。</p>

<p>模型已经创建,现在要创建的主要是:</p>

<ul>
 <li>路由:把需要支持的请求(以及请求 URL 中包含的任何信息)转发到适当的控制器函数。</li>
 <li>控制器:从模型中获取请求的数据,创建一个 HTML 页面显示出数据,并将页面返回给用户,以便在浏览器中查看。</li>
 <li>视图(模板):供控制器用来渲染数据。</li>
</ul>

<p><img alt="Express HTTP 请求/响应 路径" src="https://mdn.mozillademos.org/files/16453/Express_MVC.png"></p>

<p>因此我们需要页面来显示藏书、藏书种类、作者、藏书副本的列表和详细信息,还需要页面来创建、更新和删除记录。这些内容对于本节来说不算少,因此本节将主要集中在路由和控制器设置。本节编写的这些函数都只有框架,后续章节再扩展控制器方法,以使用模型数据。</p>

<p>第一段提供了 Express 的 <a href="http://expressjs.com/en/4x/api.html#router">Router</a> 中间件的“入门”知识。后续设置 LocalLibrary 路由时将用到这些知识。</p>

<h2 id="路由入门">路由入门</h2>

<p>路由是一段 Express 代码,它将 HTTP 动词(<code>GET</code><code>POST</code><code>PUT</code><code>DELETE</code> 等)、URL 路径/模式和处理函数三者关联起来。</p>

<p>创建路由有几种方法。本教程将使 <code><a href="http://expressjs.com/en/guide/routing.html#express-router">express.Router</a></code> 中间件,因为使用它可以将站点特定部分的路由处理程序打包,并使用通用路由前缀访问它们。我们会将所有与图书馆有关的路由保存在 <code>catalog</code> 模块中,在添加处理帐户或其他功能的路由时,可以分开保存。</p>

<div class="note">
<p><strong>注:</strong><a href="https://developer.mozilla.org/zh-CN/docs/zh-CN/docs/Learn/Server-side/Express_Nodejs/Introduction#%E5%88%9B%E5%BB%BA%E8%B7%AF%E7%94%B1%E5%A4%84%E7%90%86%E5%99%A8%EF%BC%88Route_handler%EF%BC%89">Express 简介 &gt; 创建路由处理程序</a> 简要讨论了 Express 应用的路由机制。使用 <code>Router</code> 可以保证更好的模块化(下文所述),且用法与直接在 Express 应用对象定义路由非常类似。</p>
</div>

<p>本段以下内容介绍使用 <code>Router</code> 定义路由的方法。</p>

<h3 id="定义和使用单独的路由模块">定义和使用单独的路由模块</h3>

<p>以下代码举例说明了如何创建路由模块,以及如何在 Express 应用中使用它。</p>

<p>首先,在 <strong>wiki.js </strong>模块中创建一个维基路由。代码一开始导入 Express 应用对象,用它取得一个 <code>Router</code> 对象,然后用 <code>get()</code> 方法向其添加两个具体的路由。模块的最后导出该 <code>Router</code> 对象。</p>

<pre class="brush: js notranslate">// wiki.js - 维基路由模块

const express = require('express');
const router = express.Router();

// 主页路由
router.get('/', (req, res) =&gt; {
  res.send('维基主页');
});

// “关于页面”路由
router.get('/about', (req, res) =&gt; {
  res.send('关于此维基');
});

module.exports = router;</pre>

<div class="note">
<p><strong>注:</strong>上面的路由处理回调直接定义在了路由函数中。LocalLibrary 的回调将定义在单独的控制器模块中。</p>
</div>

<p>要在主应用中使用该路由模块,首先应 <code>require</code> 它(<strong>wiki.js</strong>),然后对 Express 应用对象调用 <code>use()</code>(指定路径‘/wiki’),即可将其添加到中间件处理路径。</p>

<pre class="brush: js notranslate">const wiki = require('./wiki.js');
// ...
app.use('/wiki', wiki);</pre>

<p>这时 <code>wiki</code> 模块中定义的两个路由就可以从 <code>/wiki/</code><code>/wiki/about/</code> 访问了。</p>

<h3 id="路由函数">路由函数</h3>

<p>上述模块定义了两个典型的路由函数。<code>Router.get()</code> 方法定义的 “about” 路由(下方重现的)仅响应 HTTP GET 请求。第一个参数是 URL 路径,第二个参数是一个回调,在收到带有路径的HTTP GET 请求将调用之。</p>

<pre class="brush: js notranslate">router.get('/about', (req, res) =&gt; {
  res.send('关于此维基');
});
</pre>

<p>该回调有三个参数(通常命名为:<code>req</code><code>res</code><code>next</code>),分别是:HTTP 请求对象、HTTP 响应、中间件链中的下一个函数。</p>

<div class="note">
<p><strong>注:</strong>路由函数就是 <a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Express_Nodejs/Introduction#Using_middleware">Express 中间件</a>,这意味着它们必须(通过响应)结束请求,否则必须调用链中的 <code>next</code> 函数。上述示例使用<code>send()</code> 完成了请求,所以没有使用 <code>next</code> 参数(参数表中将其省略)。</p>

<p>上述路由函数只需要一个回调,可以根据需要指定任意数量的回调参数,或一个回调函数数组。每个函数都将加入中间件链,并且将按添加顺序调用(若有回调完成请求则中止当前周期)。</p>
</div>

<p>此处的回调对响应对象调用 <code><a href="https://expressjs.com/en/4x/api.html#res.send">send()</a></code>,当收到带有路径('<code>/about'</code>)的 GET 请求时将返回字符串“关于此维基”。还有许多其它可以结束请求/响应周期 <a href="https://expressjs.com/en/guide/routing.html#response-methods">响应方法</a>,例如,可调用 <code><a href="https://expressjs.com/en/4x/api.html#res.json">res.json()</a></code> 来发送 JSON 响应,或调用 <code><a href="https://expressjs.com/en/4x/api.html#res.sendFile">res.sendFile()</a></code> 来发送文件。构建 LocalLibrary 最常使用的响应方法是 <code><a href="https://expressjs.com/en/4x/api.html#res.render">render()</a></code>,它使用模板和数据创建并返回 HTML 文件。后续章节进一步讨论。</p>

<h3 id="HTTP_动词">HTTP 动词</h3>

<p>上面的示例使用 <code>Router.get()</code> 方法来响应特定路径的 HTTP GET 请求。</p>

<p><code>Router</code> 还为所有其他 HTTP 动词提供路由方法,大都用法相同:<code>post()</code>, <code>put()</code>, <code>delete()</code>, <code>options()</code>, <code>trace()</code>, <code>copy()</code>, <code>lock()</code>, <code>mkcol()</code>, <code>move()</code>, <code>purge()</code>, <code>propfind()</code>, <code>proppatch()</code>, <code>unlock()</code>, <code>report()</code>, ​​​​​​ <code>mkactivity()</code>, <code>checkout()</code>, <code>merge()</code>, <code>m-</code><code>search()</code>, <code>notify()</code>, <code>subscribe()</code>, <code>unsubscribe()</code>, <code>patch()</code>, <code>search()</code>, 和 <code>connect()</code></p>

<p>比如下方代码与上方 <code>/about</code> 路由行为一致,但只响应 HTTP POST 请求。</p>

<pre class="brush: js notranslate">router.post('/about', (req, res) =&gt; {
  res.send('关于此维基');
});</pre>

<h3 id="路由路径">路由路径</h3>

<p>路由路径用于定义可请求的端点。之前示例中路径都是字符串,并且必须精确写为:'/'、'/ about'、'/ book',等等。</p>

<p>路由路径也可以是字符串模式(String Pattern)。可用部分正则表达式语法来定义端点的模式。以下是所涉及的正则表达式(注意,连字符( <code>-</code>)和点(<code>.</code>)在字符串路径中解释为字面量,不能做为正则表达式):</p>

<ul>
 <li><code>?</code>:问号之前的一个字符只能出现零次或一次。例如,路由路径 <code>'/ab?cd'</code> 路径匹配端点 <code>acd</code><code>abcd</code></li>
 <li><code>+</code>:加号之前的一个字符至少出现一次。例如,路径路径 <code>'/ab+cd'</code> 匹配端点 <code>abcd</code><code>abbcd</code><code>abbbcd</code>,等。</li>
 <li><code>*</code>:星号可以替换为任意字符串。例如,路由路径 <code>'ab\*cd'</code> 匹配端点 <code>abcd</code><code>abXcd</code><code>abSOMErandomTEXTcd</code>,等。</li>
 <li><code>()</code>:将一个字符串视为一体以执行 <code>?</code><code>+</code><code>*</code> 操作。例如。 <code>'/ab(cd)?e'</code> 将对 <code>(cd)</code> 进行匹配,将匹配到 <code>abe</code><code>abcde</code></li>
</ul>

<p>路由路径也可以是 JavaScript <a href="/zh-CN/docs/Web/JavaScript/Guide/Regular_Expressions">正则表达式</a>。例如,下面的路由路径将匹配 <code>catfish</code><code>dogfish</code>,但不会匹配 <code>catflap</code><code>catfishhead</code> 等。注意,正则表达式路径不再用引号 <code>"..."</code> 括起来,而是正则表达式语法 <code>/.../</code></p>

<pre class="brush: js notranslate">app.get(/.*fish$/, (req, res) =&gt; {
  ...
});</pre>

<div class="note">
<p><strong>注:</strong>LocalLibrary 的大部分路由都只使用字符串,很少用字符串模式和正则表达式。接下来将讨论“路由参数”。</p>
</div>

<h3 id="路由参数">路由参数</h3>

<p>路径参数是命名的 URL 段,用于捕获在 URL 中的位置指定的值。命名段以冒号为前缀,然后是名称(例如 <code>/<strong>:</strong>your_parameter_name/</code>。捕获的值保存在 <code>req.params</code> 对象中,键即参数名(例如 <code>req.params.your_parameter_name</code>)。</p>

<p>举例说,一个包含用户和藏书信息的 URL:<code>http://localhost:3000/users/34/books/8989</code>,可以这样提取信息(使用 <code>userId</code><code>bookId</code> 路径参数):</p>

<pre class="brush: js notranslate">app.get('/users/:userId/books/:bookId', (req, res) =&gt; {
  // 通过 req.params.userId 访问 userId
  // 通过 req.params.bookId 访问 bookId
  res.send(req.params);
});
</pre>

<p>路由参数名必须由“单词字符”(/<code>[A-Za-z0-9_]</code>/)组成。</p>

<div class="note">
<p><strong>注:</strong>URL <em><code>/book/create</code> 会匹配 </em><code>/book/:bookId</code> 这样的路由(将提取值为'<code>create</code>' 的 '<code>bookId</code>')。第一个与传入 URL 相匹配的路由会被使用,因此 <code>/book/create</code> 的路由处理器必须定义在<em> </em><code>/book/:bookId</code> 路由之前,才能妥善处理。</p>
</div>

<p>以上就是使用路由所有的预备知识。Express 文档中还能找到更多信息:<a href="http://expressjs.com/en/starter/basic-routing.html">基础路由</a><a href="http://expressjs.com/en/guide/routing.html">路由指南</a>。以下是 LocalLibrary 路由和控制器的设置过程。</p>

<h2 id="LocalLibrary_所需路由">LocalLibrary 所需路由</h2>

<p>以下是站点页面的完整 URL 列表。<code>&lt;object&gt;</code> 是模型名称(<code>book</code><code>bookinstance</code><code>genre</code><code>author</code>),<code>&lt;objects&gt;</code> 是一组模型,<code>&lt;id&gt;</code> 是每个 Mongoose 模型实例默认的标识字段(<code>_id</code>)。</p>

<ul>
 <li><code>catalog/</code>:主页。</li>
 <li><code>catalog/&lt;objects&gt;/</code>:模型(藏书、藏书副本、藏书种类、作者)的完整列表(例如 /<code>catalog/books/</code>、/<code>catalog/genres/</code> 等)</li>
 <li><code>catalog/&lt;object&gt;/<em>&lt;id&gt;</em></code><em>:具有</em> <code><em>_id</em></code><em> </em>字段值的特定模型的详细信息页面(例如 <code>/catalog/book/584493c1f4887f06c0e67d37</code>)。</li>
 <li><code>catalog/&lt;object&gt;/create</code>:添加新模型的表单(例如 <code>/catalog/book/create</code>)。</li>
 <li><code>catalog/&lt;object&gt;/<em>&lt;id&gt;</em>/update</code>:更新具有 <code><em>_id</em></code><em> </em>字段值的特定模型的表单(例如 <code>/catalog/book/584493c1f4887f06c0e67d37/update</code>)。</li>
 <li><code>catalog/&lt;object&gt;/<em>&lt;id&gt;</em>/delete</code>:删除具有 <code><em>_id</em></code><em> </em>字段值的特定模型的表单(例如 <code>/catalog/book/584493c1f4887f06c0e67d37/delete</code>)。</li>
</ul>

<p>首页和列表页面没有包含任何额外信息。因此它们返回的结果只取决于模型类型和数据库内容,获取信息的查询操作是恒定不变的(类似地,创建对象的代码也没有较大改动)。</p>

<p>与之相反,其他 URL 是用于处理特定文档/模型实例的,它们会将项目的标识嵌入 URL(上文的 <code><em>&lt;id&gt;</em></code>)。可以用路径参数来提取嵌入的信息,并传递给路由处理器(后续章节中用于动态获取数据库中的信息)。通过在 URL 中嵌入信息,使得每种类型的所有资源只需要一个路由(例如,所有藏书副本的显示操作只需要一个路由)。</p>

<div class="note">
<p><strong>注:</strong>Express 可以通过任何方式构造 URL,可以在 URL 正文中嵌入信息(如上文),或使用 URL <code>GET</code> 参数(例如 <code>/book/?id=6</code>)。无论哪种方法,URL 都应保持整洁、合理且易读(另请参阅 <a href="https://www.w3.org/Provider/Style/URI">W3C 相关建议</a>)。</p>
</div>

<p>下面我们为上述所有 URL 创建路由处理器回调函数和路由代码。</p>

<h2 id="创建路由处理器回调函数">创建路由处理器回调函数</h2>

<p>定义路由之前应先使用单独的“控制器”模块创建回调的结构骨架。(文件/模块结构没有限制,但以下结构很适合当前项目的规模)。</p>

<p>首先在项目根目录新建一个存放控制器的文件夹(<strong>/controllers</strong>),然后为每个模型创建单独的控制器文件(模块):</p>

<pre class="notranslate">/express-locallibrary-tutorial  // 项目根目录
  <strong>/controllers</strong>
    <strong>authorController.js</strong>
    <strong>bookController.js</strong>
    <strong>bookinstanceController.js</strong>
    <strong>genreController.js</strong></pre>

<div class="blockIndicator note">
<p><strong>译注:</strong>上述四个文件可到 GitHub 下载 <a href="https://github.com/roy-tian/mdn-examples/blob/master/server/express-locallibrary-tutorial/controllers/dummyControllers.zip">dummyControllers.zip</a>。(链接已失效,请移步英文版查看具体代码)</p>
</div>

<h3 id="Author_控制器"><code>Author</code> 控制器</h3>

<p>以下是 <strong>/controllers/authorController.js</strong> 文件的内容:</p>

<pre class="brush: js notranslate">const Author = require('../models/author');

// 显示完整的作者列表
exports.author_list = (req, res) =&gt; { res.send('未实现:作者列表'); };

// 为每位作者显示详细信息的页面
exports.author_detail = (req, res) =&gt; { res.send('未实现:作者详细信息:' + req.params.id); };

// 由 GET 显示创建作者的表单
exports.author_create_get = (req, res) =&gt; { res.send('未实现:作者创建表单的 GET'); };

// 由 POST 处理作者创建操作
exports.author_create_post = (req, res) =&gt; { res.send('未实现:创建作者的 POST'); };

// 由 GET 显示删除作者的表单
exports.author_delete_get = (req, res) =&gt; { res.send('未实现:作者删除表单的 GET'); };

// 由 POST 处理作者删除操作
exports.author_delete_post = (req, res) =&gt; { res.send('未实现:删除作者的 POST'); };

// 由 GET 显示更新作者的表单
exports.author_update_get = (req, res) =&gt; { res.send('未实现:作者更新表单的 GET'); };

// 由 POST 处理作者更新操作
exports.author_update_post = (req, res) =&gt; { res.send('未实现:更新作者的 POST'); };
</pre>

<p>该模块首先导入了用于访问和更新数据的模型,然后为每个需要处理(添加、更新和删除表单,以及相应的 POST 请求,稍后在 <a href="/zh-CN/docs/learn/Server-side/Express_Nodejs/forms">使用表单</a> 一节中讲解)的 URL 导出一个函数。</p>

<p>所有函数都遵循 Express 中间件函数的标准形式,三个参数依次为:请求 <code>req</code>、响应 <code>res</code>、当前方法无法完成请求循环时(这里不存在这种情况)调用的 <code>next</code> 函数。上述方法只返回一个字符串,显示相关页面尚未创建。接收路径参数的控制器函数可将参数输出到消息字符串中(代码中出现的 <code>req.params.id</code> )。</p>

<h3 id="BookInstance、Genre、Book_控制器"><code>BookInstance</code><code>Genre</code><code>Book</code> 控制器</h3>

<p>这三个控制器与 <code>Author</code> 的模式完全相同,只是 <code>Book</code> 有一个用于显示站点欢迎页面的 <code>index()</code> 函数:</p>

<pre class="brush: js notranslate">// /controllers/bookController.js

const Book = require('../models/book');

<strong>exports.index = (req, res) =&gt; { res.send('未实现:站点首页'); };
</strong>
...
</pre>

<h2 id="创建藏书编目_catalog_路由模组">创建藏书编目 <code>catalog</code> 路由模组</h2>

<p>定义好控制器后,我们来为 LocalLibrary 网站创建完整的 URL 路由。</p>

<p>站点骨架中有一个 <strong>./routes</strong> 文件夹,其中包含两个路由文件:index 和 user,在这里新建一个 <strong>catalog.js</strong> 路由文件,如下所示:</p>

<pre class="notranslate">/express-locallibrary-tutorial   // 项目根目录
  /routes
    index.js
    users.js
    <strong>catalog.js</strong></pre>

<div class="blockIndicator note">
<p><strong>译注:</strong>可到 GitHub 下载完整的 <a href="https://raw.githubusercontent.com/roy-tian/mdn-examples/master/server/express-locallibrary-tutorial/routes/catalog.js">catalog.js</a></p>
</div>

<p><strong>/routes/</strong><strong>catalog.js</strong> 中有以下代码:</p>

<pre class="brush: js notranslate">const express = require('express');
const router = express.Router();<strong>
</strong>
// 导入控制器模块
const book_controller = require('../controllers/bookController');
const author_controller = require('../controllers/authorController');
const genre_controller = require('../controllers/genreController');
const book_instance_controller = require('../controllers/bookinstanceController');

/// 藏书路由 ///

// GET 获取藏书编目主页
router.get('/', book_controller.index);

// GET 请求添加新的藏书。注意此项必须位于显示藏书的路由(使用了 id)之前。
router.get('/book/create', book_controller.book_create_get);

// POST 请求添加新的藏书
router.post('/book/create', book_controller.book_create_post);

// GET 请求删除藏书
router.get('/book/:id/delete', book_controller.book_delete_get);

// POST 请求删除藏书
router.post('/book/:id/delete', book_controller.book_delete_post);

// GET 请求更新藏书
router.get('/book/:id/update', book_controller.book_update_get);

// POST 请求更新藏书
router.post('/book/:id/update', book_controller.book_update_post);

// GET 请求藏书
router.get('/book/:id', book_controller.book_detail);

// GET 请求完整藏书列表
router.get('/books', book_controller.book_list);

/// 藏书副本、藏书种类、作者的路由与藏书路由结构基本一致,只是无需获取主页 ///

module.exports = router;
</pre>

<p>该模块导入了 <code>express</code> 并创建了一个 <code>Router</code> 对象 <code>router</code>。所有路由都设置在 <code>router</code> 上,最后将其导出。</p>

<p><code>router</code> 对象调用 <code>.get()</code><code>.post()</code> 函数即可定义路由。这里所有路径都使用字符串定义(而不用字符串模式或正则表达式)。某些特定资源(如藏书)的路由使用路径参数从 URL 中获取对象标识。</p>

<p>处理函数均导入自上文的控制器模块。</p>

<h3 id="更新_index_路由模块">更新 index 路由模块</h3>

<p>新路由已经设置完毕,还需要设置一下 <code>index</code> 模块。我们将网站的首页重定向(<code>redirect</code>)至刚创建的地址 '<code>/catalog</code>'。</p>

<p><strong>/routes/index.js</strong> 中的中间件修改一下:</p>

<pre class="brush: js notranslate">// GET 请求主页
router.get('/', (req, res) =&gt; {
  res.redirect('/catalog');
});</pre>

<div class="note">
<p><strong>注:</strong>这是我们第一次使用 <a href="https://expressjs.com/en/4x/api.html#res.redirect">redirect()</a> 响应方法。它会使路由重定向到指定的页面,默认发送 HTTP 状态代码“302 Found”。可以根据需要更改返回的状态代码、设置绝对或相对路径。</p>
</div>

<h3 id="更新_app.js">更新 app.js</h3>

<p>最后一步是在 <code>app.js</code> 中将路由添加到中间件链。</p>

<p>打开 <strong>app.js</strong>,在 <code>index</code><code>users</code> 路由下方添加 <code>catalog</code> 路由:</p>

<pre class="brush: js notranslate">const indexRouter = require('./routes/index');
const usersRouter = require('./routes/users');
<strong>const catalogRouter = require('./routes/catalog');  // 导入 catalog 路由</strong></pre>

<p>然后在已定义的路由下方将目录路由添加进中间件栈:</p>

<pre class="brush: js notranslate">app.use('/', indexRouter);
app.use('/users', usersRouter);
<strong>app.use('/catalog', catalogRouter);  // 将 catalog 路由添加进中间件链</strong></pre>

<div class="note">
<p><strong>注:</strong>我们将图书编目模块添加到了 <code>'/catalog'</code> 路径,该路径是 catalog 模块中所有路径的前缀。例如,访问藏书列表 的URL 为:<code>/catalog/books/</code></p>
</div>

<p>大功告成。LocalLibrary 网站所需的所有 URL 的路由和框架函数都已准备完毕。</p>

<h3 id="测试路由">测试路由</h3>

<p>要测试路由,先要启动网站</p>

<ul>
 <li>默认方法
  <pre class="brush: bash notranslate"><code>$ DEBUG=express-locallibrary-tutorial:* npm start</code>
</pre>
 </li>
 <li>如果设置过 <a href="/en-US/docs/Learn/Server-side/Express_Nodejs/skeleton_website">nodemon</a>,则可以使用:
  <pre class="brush: bash notranslate">$ DEBUG=express-locallibrary-tutorial:* npm <strong style="font-family: inherit; font-size: 1rem;">run devstart</strong>
</pre>
 </li>
</ul>

<div class="blockIndicator note">
<p><strong>译注:</strong>以上命令只对 bash 有效,要在 Windows 上使用 bash,最简单的方法就是安装 Git。(详情参见 <a href="zh-CN/docs/Learn/Server-side/Express_Nodejs/skeleton_website#使用应用生成器">教程 2 相关译注</a></p>
</div>

<p>接下来浏览上面一些 URL,确保不会收到错误页面(HTTP 404)。可以尝试以下示例:</p>

<ul>
 <li><a href="http://localhost:3000/">http://localhost:3000/</a></li>
 <li><a href="http://localhost:3000/catalog">http://localhost:3000/catalog</a></li>
 <li><a href="http://localhost:3000/catalog/books">http://localhost:3000/catalog/books</a></li>
 <li><a href="http://localhost:3000/catalog/bookinstances/">http://localhost:3000/catalog/bookinstances</a></li>
 <li><a href="http://localhost:3000/catalog/authors/">http://localhost:3000/catalog/authors</a></li>
 <li><a href="http://localhost:3000/catalog/genres/">http://localhost:3000/catalog/genres</a></li>
 <li><a href="http://localhost:3000/catalog/book/5846437593935e2f8c2aa226/">http://localhost:3000/catalog/book/5846437593935e2f8c2aa226</a></li>
 <li><a href="http://localhost:3000/catalog/book/create">http://localhost:3000/catalog/book/create</a></li>
</ul>

<h2 id="小结">小结</h2>

<p>网站的路由已创建完毕,接下来的教程将把完整的实现填入控制器框架中。藉此来学习 Express 路由的基础知识,以及组织路由和控制器的一些方式。</p>

<p>下一节将使用视图 (模板) 和模型里的信息创建一个欢迎页面。</p>

<h2 id="另请参阅">另请参阅</h2>

<ul>
 <li><a href="http://www.expressjs.com.cn/starter/basic-routing.html">路由基础</a> (Express 官方文档)</li>
 <li><a href="http://expressjs.com/en/guide/routing.html">路由简介</a> (Express 官方文档)</li>
</ul>

<p>{{PreviousMenuNext("Learn/Server-side/Express_Nodejs/mongoose", "Learn/Server-side/Express_Nodejs/Displaying_data", "Learn/Server-side/Express_Nodejs")}}</p>

<h2 id="本章目录">本章目录</h2>

<ul>
 <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/Introduction">Express/Node 入门</a></li>
 <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/development_environment">设置 Node(Express)开发环境</a></li>
 <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/Tutorial_local_library_website">Express 教程:本地图书馆网站</a></li>
 <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/skeleton_website">Express 教程 2:创建站点框架</a></li>
 <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/mongoose">Express 教程 3:使用数据库(Mongoose)</a></li>
 <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/routes">Express 教程 4:路由和控制器</a></li>
 <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/Displaying_data">Express 教程 5:显示图书馆数据</a></li>
 <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/forms">Express 教程 6:使用表单</a></li>
 <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/deployment">Express 教程 7:部署至生产环境</a></li>
</ul>