目录
Simple web server
Express
Web and express
nodemon
REST
Fetching a single resource
Deleting resources
Postman
Receiving data
About HTTP request types
Middleware
现在重点转向后端,也就是转向服务器端的功能实现。
在NodeJS基础上构建的后端,是基于 Google 的 Chrome V8 引擎的 JavaScript 运行时环境。
确保 Node 版本不低于v10.18.0 (可以通过在命令行中运行 node -v 来检查版本)。
浏览器还不支持 JavaScript 的最新特性,在浏览器中运行的代码必须是babel转译过的。而在后端运行 JavaScript 的情况不同, 最新版本的 Node 支持大部分最新的 JavaScript 特性,因此可以使用最新的特性而不必转译代码。
我们的目标是实现一个后端,与 notes 应用一起工作。
从实现经典的“ hello world”应用的基础开始。
之前已经提到了npm ,这是一个用于管理 JavaScript 包的工具,来源于 Node 生态系统。
进入一个合适的目录,并使用npm init命令为应用创建一个新模板。 设置的结果会在项目根目录下自动生成的package.json 文件中,其中包含有关项目的信息。
{
"name": "backend",
"version": "0.0.1",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "Matti Luukkainen",
"license": "MIT"
}
例如,该文件定义应用的入口点是index.js 文件。
让我们对scripts 对象做一个小小的修改:
{
// ...
"scripts": {
"start": "node index.js",
"test": "echo \"Error: no test specified\" && exit 1"
},
// ...
}
接下来,创建应用的第一个版本,在项目的根目录中添加一个index.js 文件,代码如下:
console.log('hello world')
可以通过命令行直接用 Node 运行程序:
node index.js
或者将它作为一个 npm 脚本运行:
npm start
start 这个npm 脚本之所以有效,是因为 package.json 文件中定义了它:
{
// ...
"scripts": {
"start": "node index.js",
"test": "echo \"Error: no test specified\" && exit 1"
},
// ...
}
尽管通过从命令行调用 node index.js 来启动项目是可以工作的,但 npm 项目通常执行 npm 脚本之类的任务。
默认情况下,package.json 文件还定义了另一个常用的 npm 脚本,称为npm test。 由于我们的项目还没有测试库,npm test 命令只是执行如下命令:
echo "Error: no test specified" && exit 1
【简单的 web 服务器】
把这个应用改成一个 web 服务器:
const http = require('http')
const app = http.createServer((request, response) => {
response.writeHead(200, { 'Content-Type': 'text/plain' })
response.end('Hello World')
})
const PORT = 3001
app.listen(PORT)
console.log(`Server running on port ${PORT}`)
一旦运行应用,控制台中就会输出如下消息:
Server running on port 3001
我们可以在浏览器中通过访问地址 http://localhost:3001 打开我们的应用:
事实上,无论 URL 的后半部分是什么,服务器的工作方式都是相同的。 地址http://localhost:3001/foo/bar 也会显示相同的内容。
注意:如果端口3001已经被其他应用使用,那么启动服务器将产生如下错误消息:
➜ hello npm start
> [email protected] start /Users/mluukkai/opetus/_2019fullstack-code/part3/hello
> node index.js
Server running on port 3001
events.js:167
throw er; // Unhandled 'error' event
^
Error: listen EADDRINUSE :::3001
at Server.setupListenHandle [as _listen2] (net.js:1330:14)
at listenInCluster (net.js:1378:12)
要么关闭使用3001端口应用,要么为此应用使用不同的端口。
代码的第一行:
const http = require('http')
导入 Node 的内置 web server模块。 这实际上是我们在浏览器端代码中已经做过的事情,只是语法稍有不同:
import http from 'http'
在浏览器中运行的代码使用 ES6模块。 模块定义为export ,并与import一起使用。
而Node.js 使用 CommonJS。 原因在于,早在 JavaScript 在语言规范中支持模块之前,Node 生态系统就有对模块需求,Node 还不支持 ES6模块 。
Commonjs 模块的功能几乎完全类似于 ES6模块。
代码中的下一块:
const app = http.createServer((request, response) => {
response.writeHead(200, { 'Content-Type': 'text/plain' })
response.end('Hello World')
})
该代码使用 http 模块的 createServer 方法创建一个新的 web 服务器。 一个事件处理 被注册到服务器,每次 向服务器的地址http://localhost:3001 发出 HTTP 请求时,它就被调用。
响应请求的状态代码为200,Content-Type 头文件设置为 text/plain,将返回站点的内容设置为Hello World。
最后一行将绑定的HTTP 服务器分配给 app 变量 ,并监听发送到端口3001的 HTTP 请求:
const PORT = 3001
app.listen(PORT)
console.log(`Server running on port ${PORT}`)
需要后端服务器的主要用途是向前端提供 JSON 格式的原始数据。 更改服务器,返回 JSON 格式的“硬编码”便笺列表:
const http = require('http')
let notes = [
{
id: 1,
content: "HTML is easy",
date: "2019-05-30T17:30:31.098Z",
important: true
},
{
id: 2,
content: "Browser can execute only Javascript",
date: "2019-05-30T18:39:34.091Z",
important: false
},
{
id: 3,
content: "GET and POST are the most important methods of HTTP protocol",
date: "2019-05-30T19:20:14.298Z",
important: true
}
]
const app = http.createServer((request, response) => {
response.writeHead(200, { 'Content-Type': 'application/json' })
response.end(JSON.stringify(notes))
})
const PORT = 3001
app.listen(PORT)
console.log(`Server running on port ${PORT}`)
重新启动服务器(可以通过在控制台中按 Ctrl + c 关闭服务器) ,并刷新浏览器。
Content-Type 头中的 application/json 值通知接收方数据为 JSON 格式。 使用 JSON.stringify(notes) 方法将 notes 数组转换为 JSON。
当打开浏览器的时候,显示的格式:
直接使用 Node 内置的http web 服务器实现我们的服务器代码是可行的。 但是当应用规模变大时会很麻烦。
许多库提供比内置的 http 模块更友好的界面,以简化使用 Node 作为服务器端开发。express为构建后台服务器的一般的用例提供一个很好的抽象。
通过下面的命令将它定义为一个项目依赖,来开始使用 express:
npm install express
该依赖项也被添加到了我们的package.json 文件中:
{
// ...
"dependencies": {
"express": "^4.17.1"
}
}
依赖的源代码安装在项目根目录中的 node_modules 目录中。
这些实际上是express的依赖项,以及它所有依赖项的依赖项,等等。 这些被称为项目的 传递依赖transitive dependencies 。
使用如下命令更新项目的依赖:
npm update
同样,如果在另一台计算机上开始工作,可以使用如下命令安装package.json 中定义的项目的所有最新依赖项:
npm install
如果依赖项的major值没有改变,那么新版本应该是向后兼容backwards compatible。 这意味着,如果我们的应用在将来碰巧使用了 express 的版本4.99.175,那么在这个部分中实现的所有代码仍然可以在不对代码进行更改的情况下正常工作。 相比之下,未来的5.0.0。 Express版本 可能包含may contain更改,将导致应用不能正常工作。
回到应用,并进行如下更改:
const express = require('express')
const app = express()
let notes = [
...
]
app.get('/', (req, res) => {
res.send('Hello World!
')
})
app.get('/api/notes', (req, res) => {
res.json(notes)
})
const PORT = 3001
app.listen(PORT, () => {
console.log(`Server running on port ${PORT}`)
})
必须重新启动应用来更新服务器状态。
代码的开头导入了 express,这次是一个function ,用于创建一个存储在 app 变量中的 express 应用:
const express = require('express')
const app = express()
接下来,定义应用的两个路由。 第一个定义了一个事件处理,用于处理对应用的 / root 发出的 HTTP GET 请求:
app.get('/', (request, response) => {
response.send('Hello World!
')
})
事件处理接受两个参数。 第一个request 参数包含 HTTP 请求的所有信息,第二个 response 参数用于定义请求的响应方式。
代码中,请求是通过使用 response 对象的send 方法来应答的。 调用该方法,使服务器通过发送
字符串,以response响应 HTTP 请求。 由于参数是一个字符串,所以 express 会自动将Content-Type 头的值设置为 text/html.。 响应的状态代码默认为200。Hello World!
可以通过开发工具中的Network 选项卡来验证这一点:
第二个路由定义了一个事件处理,它处理对应用的notes 路径发出的 HTTP GET 请求:
app.get('/api/notes', (request, response) => {
response.json(notes)
})
请求用response对象的json方法进行响应。 调用该方法会将notes 数组作为 JSON 格式的字符串进行传递。 Express 自动设置Content-Type 头文件,其值为 application/json。
Node 的早期版本必须使用 JSON.stringify 方法将数据转换为 JSON 格式:
response.end(JSON.stringify(notes))
而 express自动转换,不再需要这样做。
JSON是一个字符串,而不是像分配给 notes 的值那样的 JavaScript 对象。
下面的实验可以说明这一点:
上面的实验是在交互式的node-repl中完成的。 可以通过在命令行中键入 node 来启动交互式 node-repl。 在编写应用代码时,对于测试命令的工作方式,repl 特别有用,强烈推荐!
对应用的代码进行更改后,必须重新启动应用以更新。 键入 ⌃+C 首先关闭应用,然后重新启动应用。 与 React 中方便的工作流程相比,Node就有点麻烦,在 React 中,浏览器会在进行更改后自动重新加载。
解决这个问题的方法是使用nodemon :
nodemon 将监视启动 nodemon 的目录中的文件,如果任何文件发生更改,nodemon 将自动重启node应用。
nodemon 定义为开发依赖development dependency:
npm install --save-dev nodemon
package.json 的内容也发生了变化:
{
//...
"dependencies": {
"express": "^4.17.1",
},
"devDependencies": {
"nodemon": "^2.0.2"
}
}
如果不小心敲错了命令,并且 nodemon 依赖项被添加到“ dependencies”而不是“ devDependencies” ,那么手动更改package.json 的内容以匹配上面显示的内容也是可以的。
开发依赖会指向仅在应用开发过程中需要的工具,例如用于测试或自动重启应用的工具,如nodemon。
当应用在生产服务器(例如 Heroku)的生产模式下运行时,并不需要这些开发依赖项。
可以用nodemon 这样来启动我们的应用:
node_modules/.bin/nodemon index.js
对应用代码的更改会导致服务器自动重新启动。 但即使后端服务器自动重启,浏览器仍然需要手动刷新。
在package.json 文件中将这个命令定义一个专用的npm 脚本:
{
// ..
"scripts": {
"start": "node index.js",
"dev": "nodemon index.js",
"test": "echo \"Error: no test specified\" && exit 1"
},
// ..
}
在脚本中,不需要指定node_modules/.bin/nodemon 到 nodemon ,因为 npm 自己知道从该目录搜索文件。
现在可以在开发模式下使用如下命令启动服务器:
npm run dev
与start 和test 脚本不同,还必须将run 添加到命令中。
扩展应用,使它提供像json-server那样的 RESTful HTTP API 。
Representational State Transfer,又名REST,是一种架构风格,用于构建可伸缩的 web 应用。
我们只关注web应用对 RESTful API 的典型理解, narrow view。 Rest 的最初定义实际上并不局限于 web 应用。
应用中像便笺这样的单数实体,在 RESTful thinking 中称为resource。 每个resource都有一个相关联的 URL,这个 URL 是资源的唯一地址。
一个约定是结合resource 类型名称和resource的唯一标识符来创建resource唯一的地址。
假设服务的根 URL 是 www.example.com/api 。
如果我们将便笺的资源类型定义为note,那么标识为10的便笺资源的地址就是唯一的地址www.example.com/api/notes/10。
所有便笺资源的整个集合的 URL 是 www.example.com/api/notes 。
我们可以对资源执行不同的操作。要执行的操作由 HTTP动词 verb 定义:
URL | verb | functionality |
---|---|---|
notes/10 | GET | fetches a single resource |
notes | GET | fetches all resources in the collection |
notes | POST | creates a new resource based on the request data |
notes/10 | DELETE | removes the identified resource |
notes/10 | PUT | replaces the entire identified resource with the request data |
notes/10 | PATCH | replaces a part of the identified resource with the request data |
以上粗略地定义 REST 所指的 统一接口 uniform interface ,一种一致的定义接口的方式,使系统能够进行合作。
【获取一个单一资源】
扩展应用,以提供一个 REST 接口,用于操作单个便笺。 首先创建一个路由来获取单个资源。
我们为单个便笺使用的唯一地址是 notes/10,其中末尾的数字指的是便笺的唯一 id 号。
可以使用冒号语法为express路由定义参数 :
app.get('/api/notes/:id', (request, response) => {
const id = request.params.id
const note = notes.find(note => note.id === id)
response.json(note)
})
现在, app.get('/api/notes/:id', ...)
将处理所有的 HTTP GET 请求,这些请求的格式是/api/notes/SOMETHING,其中SOMETHING 是任意的字符串。
请求路由中的id 参数可以通过request对象访问:
const id = request.params.id
使用 find 方法查找与 id 参数匹配的的便笺,返回给request的发送者。
当在浏览器中键入 http://localhost:3001/api/notes/1 测试应用时,不能正常工作,浏览器显示一个空白页面。
在代码中添加 console.log 命令是一个久经验证的技巧:
app.get('/api/notes/:id', (request, response) => {
const id = request.params.id
console.log(id)
const note = notes.find(note => note.id === id)
console.log(note)
response.json(note)
})
在浏览器中再次访问 http://localhost:3001/api/notes/1 时,终端控制台将显示如下内容:
来自 route 的 id 参数被传递给应用,但是 find 方法没有找到匹配的便笺。
在传递给 find 方法的比较函数中添加console log。
app.get('/api/notes/:id', (request, response) => {
const id = request.params.id
const note = notes.find(note => {
console.log(note.id, typeof note.id, id, typeof id, note.id === id)
return note.id === id
})
console.log(note)
response.json(note)
})
当我们在浏览器中再次访问 URL 时,对比较函数的每次调用都会向控制台打印一些不同的内容。 控制台输出如下:
1 'number' '1' 'string' false
1‘ number’’1’‘ string’ false
2 'number' '1' 'string' false
2‘ number’’1’‘ string’ false
3 'number' '1' 'string' false
3‘ number’’1’‘ string’ false
这个错误的原因很清楚了。 id 变量包含一个字符串“1” ,而便笺的 id 是整数。 在 JavaScript 中,“三个等号 triple equals”比较默认认为不同类型的所有值都不相等,这意味着1不等于“1”。
将 id 参数从一个字符串更改为一个number来解决这个问题:
app.get('/api/notes/:id', (request, response) => {
const id = Number(request.params.id)
const note = notes.find(note => note.id === id)
response.json(note)
})
现在可以正常获取单个资源了。
然而应用还有另一个问题。如果我们搜索一个 id 不存在的便笺,服务器会响应:
返回的 HTTP状态码还是200,这意味着响应成功了。 content-length 标头的值为0,因为没有将数据与响应一起发送回来。
出现此行为的原因是,如果没有找到匹配的便笺,则将note变量设置为了undefined。 服务器应该用状态码404 not found响应,而不是200。
对代码进行如下更改:
app.get('/api/notes/:id', (request, response) => {
const id = Number(request.params.id)
const note = notes.find(note => note.id === id)
if (note) {
response.json(note)
} else {
response.status(404).end()
}
})
由于响应没有附加任何数据,我们使用status方法来设置状态,并使用end方法来响应request而不发送任何数据。
现在应用正常工作,如果没有找到便笺,则发送错误状态代码。 然而,应用不会返回任何东西显示给用户,就像我们 在web 应用访问一个不存在的页面时所做的那样。 不需要在浏览器中显示任何内容,因为 REST API 是用于编程使用的接口,只需要错误状态代码就行了。
【删除资源】
接下来实现一个删除资源的路由。 通过向资源的 url 发出 HTTP DELETE 请求来删除:
app.delete('/api/notes/:id', (request, response) => {
const id = Number(request.params.id)
notes = notes.filter(note => note.id !== id)
response.status(204).end()
})
删除资源成功意味着便笺存在并被删除,用状态码204 no content响应请求,并返回没有数据的响应。
如果资源不存在,对于应该向 DELETE 请求返回什么状态代码并没有共识。 实际上,只有204和404两个可选项。 为了简单起见,我们的应用在这两种情况下都将响应204。
可以通过浏览器进行 HTTP GET 请求很容易测试删除操作。
使用工具让后端的测试变得更加容易。 其中之一就是命令行程序curl 。这里我们将使用 Postman 来测试应用。
安装 Postman 并尝试一下:
使用Postman在这种情况下很容易。 定义 url 然后选择正确的请求类型就足够了。
后端服务器响应正确。 通过向http://localhost:3001/api/notes 发出 HTTP GET 请求,可以看到 id 为2的便笺已经不在列表中,表明删除成功。
因为应用的便笺只保存到了内存中,所以当重新启动应用时,便笺列表将返回到原始状态。
【接受数据】
接下来向服务器添加新便笺。 通过向地址 HTTP://localhost:3001/api/notes 发送一个 HTTP POST 请求,并以 JSON 格式在请求body中发送新便笺的所有信息,就可以添加一个便笺。
express json-parser帮助我们方便地访问数据,它与命令app.use(express.json())一起使用。
激活 json-parser 并实现一个处理 HTTP POST 请求的初始处理程序:
const express = require('express')
const app = express()
app.use(express.json())
//...
app.post('/api/notes', (request, response) => {
const note = request.body
console.log(note)
response.json(note)
})
事件处理函数可以从request 对象的body 属性访问数据。
如果没有 json-parser,body 属性将是undefined的。 Json-parser 的功能是获取请求的 JSON 数据,将其转换为 JavaScript 对象,然后在调用路由处理程序之前将其附加到请求对象的 body 属性。
目前,除了将接收到的数据打印到控制台并在响应中将其发送回来之外,应用并不对其执行任何操作。
在实现应用逻辑的剩余部分之前,让我们先用 Postman 验证服务器实际接收到的数据。 除了在 Postman 中定义 URL 和请求类型外,还必须定义body 中发送的数据:
该应用将我们在请求中发送到控制台的数据打印出来:
注意:在后端工作时,应该让运行应用的终端始终可见。 Nodemon允许我们对代码所做的任何更改都将重新启动应用。注意控制台,会立即发现应用中出现的错误:
类似地,检查控制台以确保后端在不同情况下的行为与我们期望的一样,比如在使用 HTTP POST 请求发送数据时。 当然,在开发应用时向代码中添加一些 console.log 命令是一个不错的主意。
导致问题的一个潜在原因是在请求中错误地设置了Content-Type 头。 如果body类型没有正确定义,这种情况可能发生在 Postman 身上:
Content-Type 的header设置为了 text/plain:
服务器似乎只接收到一个空对象:
如果头部没有设置正确的值,服务器将无法正确解析数据。
如果在代码中的某个位置使用 console.log(request.headers) 命令打印所有请求头,那么您将能够发现缺少了Content-Type 头。
回到应用。 一旦我们知道应用正确地接收了数据,就可以处理最终请求了:
app.post('/api/notes', (request, response) => {
const maxId = notes.length > 0
? Math.max(...notes.map(n => n.id))
: 0
const note = request.body
note.id = maxId + 1
notes = notes.concat(note)
response.json(note)
})
我们需要唯一的 id。 首先,找出当前列表中最大的 id 号,并将其赋值给 maxId 变量。 然后将新通知的 id 定义为 maxId + 1。
现在仍然存在 HTTP POST 请求可添加任意属性的问题。 可以通过定义content 属性不能为空来改进应用。important 和date 属性将被赋予默认值。 所有其他属性都被丢弃:
const generateId = () => {
const maxId = notes.length > 0
? Math.max(...notes.map(n => n.id))
: 0
return maxId + 1
}
app.post('/api/notes', (request, response) => {
const body = request.body
if (!body.content) {
return response.status(400).json({
error: 'content missing'
})
}
const note = {
content: body.content,
important: body.important || false,
date: new Date(),
id: generateId(),
}
notes = notes.concat(note)
response.json(note)
})
为便笺生成新 id 号的逻辑已经提取到一个单独的 generateId 函数中。
如果接收到的数据缺少content 属性的值,服务器将使用状态码400 bad request响应请求:
if (!body.content) {
return response.status(400).json({
error: 'content missing'
})
}
调用 return 是至关重要的,否则代码将执行到最后才能将格式不正确的通知保存到应用中。
如果 content 属性具有值,则说明便笺内容将基于接收到的数据。 前面提到,在服务器上生成时间戳比在浏览器上生成更好,因为我们不能确保运行浏览器的主机的时钟设置是正确的。 现在由服务器生成date 属性。
如果缺少important 属性,则将该值默认为false。 当前生成默认值的方式相当奇怪:
important: body.important || false,
如果保存在 body 变量中的数据具有important 属性,则表达式将计算它作为值。 如果该属性不存在,那么表达式将默认为 false,该表达式在双竖线的右侧定义。
还有一件事,生成 id 的函数现在是这样的:
const generateId = () => {
const maxId = notes.length > 0
? Math.max(...notes.map(n => n.id))
: 0
return maxId + 1
}
函数体包含一行内容:
Math.max(...notes.map(n => n.id))
这notes.map(n => n.id) 创建一个包含所有便笺 id 的新数组。 Math.max返回传递给它的数的最大值。 然而,notes.map(n => n.id) 是一个数组,因此它不能直接作为 Math.max 的参数。 数组可以通过使用“ 三个点...”展开语法 转换为单独的数字。
【关于 HTTP 请求类型】
HTTP 标准讨论了与请求类型相关的两个属性,安全 和 幂等性 。
Http GET 请求应该满足安全性:
In particular, the convention has been established that the GET and HEAD methods SHOULD NOT have the significance of taking an action other than retrieval. These methods ought to be considered "safe".
特别是,已经建立了一个约定,即 GET 和 HEAD 方法除了检索之外不应该有其他行动的含义。 这些方法应该被认为是“安全的”。
安全性意味着执行请求不能在服务器中引起任何副作用。 副作用是指数据库的状态不能因请求而改变,响应只能返回服务器上已经存在的数据。
没有什么能够保证 GET 请求实际上是安全的,这实际上只是 HTTP 标准中定义的一个建议。 通过遵守我们的 API 中的 RESTful 原则,GET 请求实际上总是以一种安全safe 的方式使用。
Http 标准还定义了应该是安全的请求类型HEAD。 HEAD 应该像 GET 一样工作,但是它只返回状态码和响应头。 发出 HEAD 请求不会返回响应主体。
除了 POST 之外的所有 HTTP 请求都应该是幂等:
Methods can also have the property of "idempotence" in that (aside from error or expiration issues) the side-effects of N > 0 identical requests is the same as for a single request. The methods GET, HEAD, PUT and DELETE share this property
方法也可以具有“幂等”属性,即(除了错误或过期问题) N > 0 相同请求的副作用与单个请求相同。 方法 GET、 HEAD、 PUT 和 DELETE 都具有此属性
如果一个请求有副作用,那么无论发送多少次请求,结果都应该是相同的。
如果我们对 /api/notes/10 发出 HTTP PUT 请求,并且在发出请求时发送数据{ content: "no side effects!", important: true },结果是相同的,不管请求被发送多少次。
就像 GET 请求的安全性 一样,幂等也只是 HTTP 标准中的一个推荐,而不是仅仅基于请求类型就可以保证的东西。 但是,当我们的 API 遵循 RESTful 原则时,GET、 HEAD、 PUT 和 DELETE 请求的使用方式是等幂的。
Post 是唯一既不是安全性 也不是幂等 的 HTTP 请求类型。 如果我们向 /api/notes 发送5个不同的 HTTP POST 请求,其中包含 {content: "many same", important: true},那么服务器上得到的5个便笺将具有相同的内容。
【中间件】
之前使用的 express json-parser是所谓的中间件。
中间件是可用于处理请求和响应对象的函数。
json-parser 从请求对象中存储的请求中获取原始数据,将其解析为一个 JavaScript 对象,并将其作为一个新的属性、body 分配给请求对象。
在实践中,可以同时使用多个中间件。 有多个的时候,将按照顺序,一个接一个地执行。
实现中间件,打印有关发送到服务器的每个请求的信息。
中间件是一个接收三个参数的函数:
const requestLogger = (request, response, next) => {
console.log('Method:', request.method)
console.log('Path: ', request.path)
console.log('Body: ', request.body)
console.log('---')
next()
}
在函数体的末尾,调用作为参数传递的下一个函数。 函数将控制权交给下一个中间件。
中间件是这样使用的:
app.use(requestLogger)
中间件函数按照与express服务器对象的使用方法一起使用的顺序调用。 注意,json-parser 是在 requestLogger 中间件之前使用的,否则在执行日志记录器时,不会初始化 request.body !
如果希望在调用路由事件处理程序之前执行中间件函数,则必须在路由之前使用中间件函数。 还有一些情况,我们希望在路由之后定义中间件函数,这意味着我们定义的中间件函数只有在没有路由处理 HTTP 请求的情况下才被调用。
在路由之后添加如下中间件,用于捕获对不存在的路由发出的请求。 对于这些请求,中间件将返回 JSON 格式的错误消息。
const unknownEndpoint = (request, response) => {
response.status(404).send({ error: 'unknown endpoint' })
}
app.use(unknownEndpoint)