让我们看看如何使用Express建立一个简单的REST api来实现所有的CRUD操作。REST是指Representational State Transfer。它是一种通过客户端和服务器架构建立HTTP服务的惯例。在REST中,HTTP协议被用来促进服务器上资源的创建、读取、更新和删除。这些操作可以统称为CRUD操作。每个操作都使用不同的HTTP动词。创建使用POST请求。读取使用的是GET请求。更新使用的是PUT请求。最后,删除使用DELETE请求。在本教程中,我们将使用Node.js和Express创建所有这些服务。
Express网络服务器
我们已经学会了如何构建一个Express项目,所以让我们继续创建一个新的项目,以便我们可以建立新的REST api。输入以下命令开始吧。
- node $
mkdir express-rest - node $
cd express-rest - express-rest $
npm init --yes

我们还需要在项目的根部有一个index.js文件,所以我们也可以创建它。一旦完成,添加以下代码。我们需要express模块,然后调用express()函数,并将结果分配给app 常量。这个结果是一个对象,按照惯例,它通常被命名为app。Visual Studio Code告诉我们关于express()的以下信息。"创建一个Express应用程序。express()函数是Express模块导出的顶级函数"。
const express = require('express');
const app = express();
app.get('/', (req, res) => {
res.send('Oh Hi There!');
});
app.listen(3000, () => console.log('Listening on port 3000'));
我们可以通过在命令提示符下键入node index.js 来启动网络服务器。

现在服务器正在运行,我们可以在浏览器中加[载http://localhost:3000,看看一切是否正常。

现在我们将添加一个新的路由,模拟一个API。我们希望能够访问/api/games端点,看到系统中所有的游戏。在这一点上,我们没有数据库,所以我们将只是填充一个简单的数组作为例子。
const express = require('express');
const app = express();
app.get('/', (req, res) => {
res.send('Oh Hi There!');
});
app.get('/api/games', (req, res) => {
res.send(['Mario', 'Zelda', 'Donkey Kong']);
});
app.listen(3000, () => console.log('Listening on port 3000'));
为了测试这个,你需要在命令行重新启动网络服务器。首先,键入CTRL-C来停止服务器,然后用节点index.js ,再次重新启动服务器。现在,继续在浏览器中访[问http://localhost:3000/api/games,看一看吧

Nodemon Express
随着我们的发展,每次更新都要手动重启服务器,这将是一件令人厌烦的事情。我们可以通过在启动服务器时使用nodemon来轻松解决这个问题。如果你看了我们的用节点渲染HTML的教程,Nodemon很可能已经安装了。如果没有安装,只需继续在命令行中输入npm i -g nodemon 。一旦准备就绪,停止服务器并使用nodemon index.js 重新启动它。

现在,当你对你的文件进行修改时,比如这里的第5行。
const express = require('express');
const app = express();
app.get('/', (req, res) => {
res.send('Ima change this up!');
});
app.get('/api/games', (req, res) => {
res.send(['Mario', 'Zelda', 'Donkey Kong']);
});
app.listen(3000, () => console.log('Listening on port 3000'));
你将不再需要停止并重新启动服务器。继续进行你的更新,你会看到命令行自动反映了这一点。

当然,浏览器中的结果也会更新。

用环境变量设置端口
到目前为止,我们一直在为不同的应用程序变量(如监听端口)进行硬编码。一旦你建立了比教程程序更大的程序,你就不会想这么做了。处理这个问题的更合适的方法是检查一个名为PORT的环境变量,如果它被设置了,就使用这个值。如果没有设置,你可以退回到合理的默认值。注意这里的新代码。
const express = require('express');
const app = express();
app.get('/', (req, res) => {
res.send('Ima change this up!');
});
app.get('/api/games', (req, res) => {
res.send(['Mario', 'Zelda', 'Donkey Kong']);
});
const port = process.env.PORT || 3000;
app.listen(port, () => console.log(`Listening on port ${port}...`));
现在,端口可以被设置并从环境变量中读取。在这里,我们将端口设置为4000,而不是我们使用的3000。注意,当我们重新启动nodemon时,服务器现在正在监听端口4000。

Express Route Parameters
单一参数
路由参数是至为重要的。正是通过传递路由参数,用户可以向服务器指定它要获取的资源。请看下面的代码。
app.get('/api/games/:id', (req, res) => {
res.send(req.params.id);
});
这说明,无论何时向/api/games 发出获取请求,后面的任何内容都是一个动态的路由参数。因此,例如,如果我们访[问http://localhost:4000/api/games/25,那么25就是路由参数。要在代码中获取这个,你可以使用req.params.id。这里我们访问http://localhost:4000/api/games/donkeykong,注意我们只是在浏览器中显示路由参数的值。

多参数
你可以在url中拥有一个以上的路由参数。考虑一下这段代码。
app.get('/api/games/:title/:publisher', (req, res) => {
res.send(req.params);
});
现在,如果我们访[问http://localhost:4000/api/games/mario/nintendo,这里是我们得到的结果。

查询参数
我们也可以在url中访问查询参数。在这里,我们更新代码,允许路由参数和查询参数。
app.get('/api/games/:title/:publisher', (req, res) => {
res.send([req.params, req.query]);
});

路由参数用于主要的和需要的值,以便与一个资源一起工作。另一方面,查询参数在本质上可以被认为是可选的。
HTTP GET请求
现在我们可以设置一些获取请求,从服务器上获取游戏。这对应于rest api中的readof crud。我们只是使用一个游戏数组,因为现在还没有数据库。所以让我们建立一个游戏数组,就像我们在这里看到的那样。
const games = [{
id: 1,
title: 'Mario'
},
{
id: 2,
title: 'Zelda'
},
{
id: 3,
title: 'Donkey Kong'
}
];
通常在RESTful惯例中,如果你向api发出一个没有指定路由参数的获取请求,那么你应该得到所有资源。因此,如果我们访问/api/games,那么我们应该看到所有的游戏。这里的代码应该可以做到这一点。
// get all games
app.get('/api/games', (req, res) => {
res.send(games);
});
看起来不错!

现在我们想只用一个路由参数来找到一个特定的游戏。为此,我们可以使用find函数。
// get game by id
app.get('/api/games/:id', (req, res) => {
const game = games.find(g => g.id === parseInt(req.params.id));
if (!game) return res.status(404).send('The game with the given ID was not found.');
res.send(game);
});
这也是很好的工作!当我们提供路由参数3 ,我们得到的是大金刚游戏。

HTTP POST请求
现在我们需要设置代码,使我们的网络服务器能够响应http post请求。这相当于在一个rest api中创建crud。我们可以使用一个post请求来向系统添加一个新游戏。注意这里的附加代码。
const express = require('express');
const app = express();
app.use(express.json());
const games = [{
id: 1,
title: 'Mario'
},
{
id: 2,
title: 'Zelda'
},
{
id: 3,
title: 'Donkey Kong'
}
];
// get all games
app.get('/api/games', (req, res) => {
res.send(games);
});
// get game by id
app.get('/api/games/:id', (req, res) => {
const game = games.find(g => g.id === parseInt(req.params.id));
if (!game) return res.status(404).send('The game with the given ID was not found.');
res.send(game);
});
// add a game
app.post('/api/games', (req, res) => {
const game = {
id: games.length + 1,
title: req.body.title
}
games.push(game);
res.send(game);
});
const port = process.env.PORT || 3000;
app.listen(port, () => console.log(`Listening on port ${port}...`));
我们在上面的片段中首先注意到的是app.use(express.json());一行。这说明我们正在向我们的应用程序添加一个中间件。我们需要这样做的原因是我们需要从post请求的正文中解析标题属性,即req.body.title。Express需要能够解析请求正文中的json对象,所以这个中间件将其打开。
接下来,我们看到我们正在向集合发布,或者换句话说是向/api/games发布。这个例子有点像黑客,因为我们实际上不是在和数据库打交道,但它能让我们明白这个道理。这就是为什么我们通过使用games.length + 1来获得id。标题将通过req.body.title从post请求中的json对象解析出来。最后,我们简单地将新游戏推送到我们的游戏数组中,然后按照惯例将游戏作为一个响应发送回来。
用Postman测试端点
我们可以通过使用postman来测试发送一个在请求正文中含有json对象的post请求。我们指定请求类型为POST,[在http://localhost:4000/api/games,提供游戏集合的路由,并设置json对象包括一个标题为Splatoon的游戏。

一旦我们点击发送,我们应该得到一个响应,就像我们在这里看到的那样,有一个新的ID为4和游戏Splatoon的标题。

因此,看起来一切都工作得很完美!这意味着,如果我们现在制作简单的游戏,那么我们就可以把它变成一个新的游戏。这意味着,如果我们现在向api/games的同一个集合api发出简单的GET请求,那么我们现在应该看到4个游戏,而不是我们原来的3个,这正是我们得到的。

Joi输入验证
当设置一个api时,确保验证发送到api的任何数据是很重要的。你不能相信用户或其他应用程序可能向你的api发送的任何数据。在使用Express时,一个很好的设置输入验证的方法是使用流行的Joi包。让我们用npm来安装它吧!

一旦安装了Joi,就像这样把它要求到文件中。
const Joi = require('joi');
现在我们可以像这样更新app.post()的调用。首先,我们定义我们需要的模式或验证规则。这里我们简单地说,标题必须至少有两个字符。然后,我们调用validate()方法,输入请求体和模式。从那里,我们只是检查错误,如果有任何错误,就把它们作为一个响应送回来。
// add a game
app.post('/api/games', (req, res) => {
const schema = {
title: Joi.string().min(2).required()
};
const result = Joi.validate(req.body, schema);
if (result.error) {
res.status(400).send(result.error)
}
const game = {
id: games.length + 1,
title: req.body.title
}
games.push(game);
res.send(game);
});
很好!现在,我们可以发送一个错误的请求。现在,我们可以再次使用postman发送一个错误的请求,看看结果。

我们得到的响应是一个错误,就像我们所期望的那样。
{
"isJoi": true,
"name": "ValidationError",
"details": [
{
"message": "\"title\" length must be at least 2 characters long",
"path": [
"title"
],
"type": "string.min",
"context": {
"limit": 2,
"value": "x",
"key": "title",
"label": "title"
}
}
],
"_object": {
"title": "x"
}
}
HTTP PUT请求
要更新服务器上的一个现有资源,你可以使用PUT请求。这对应于rest api中的Updateof crud。让我们看看如何设置代码来处理PUT请求,以便我们可以在应用程序中更新一个游戏。这只是稍微复杂一些。让我们先回顾一下我们需要完成的步骤。
- 在应用程序中查找游戏
- 如果没有找到,返回一个404错误
- 验证发送至服务器的数据
- 如果该数据无效,则发送一个错误的400错误请求
- 如果一切正常,更新游戏
- 将更新后的游戏作为一个响应发送回来
在我们的代码中,这将转化为类似这样的东西。
// update a game
app.put('/api/games/:id', (req, res) => {
const game = games.find(g => g.id === parseInt(req.params.id));
if (!game) return res.status(404).send('The game with the given ID was not found.');
const schema = {
title: Joi.string().min(2).required()
};
const result = Joi.validate(req.body, schema);
if (result.error) {
res.status(400).send(result.error)
}
game.title = req.body.title;
res.send(game);
});
让我们来测试一下!我们将向服务器发送一个PUT请求,指定ID为3,并在请求的正文中传递一个json对象,为这个游戏添加一个新的标题。现在,id为3的游戏是 "大金刚"。我们将尝试把它改为 "洞穴故事"。

我们得到的回复与我们期望的一样。

最后,我们只是想再次向浏览器中的集合发出一个GET请求,游戏3现在应该是'洞穴故事'。

HTTP删除请求
最后,我们将学习如何在我们的rest api中实现删除渣滓的操作。我们可以遵循与更新课程类似的逻辑。下面是我们可以添加的代码。
// delete a game
app.delete('/api/games/:id', (req, res) => {
const game = games.find(g => g.id === parseInt(req.params.id));
if (!game) return res.status(404).send('The game with the given ID was not found.');
const index = games.indexOf(game);
games.splice(index, 1);
res.send(game);
});
现在,让我们在postman中发送一个DELETE请求,删除id为2的游戏。

我们得到被删除的游戏作为响应,这就是我们所期望的。

最后,我们再一次向api发出GET请求,列出我们所有的游戏。我们可以看到,游戏2现在不见了。哦,不!塞尔达不要走!"。

Node.js Express Rest Api教程总结
在本教程中,我们学习了所有关于使用Node.js和Express建立一个简单的REST api。我们涵盖了主要的动词,如GET、POST、PUT和DELETE,以及所有的CRUD操作。Express使设置这些操作变得非常容易,如app.get()、app.post()、app.put()和app.delete()。此外,我们还可以使用Mongoose进行CRUD。
一些需要记住的关键点
-
REST定义了一套创建HTTP服务的约定。
-
POST:创建一个资源
-
GET:读取一个资源
-
PUT:更新一个资源
-
DELETE:删除一个资源
-
你可以使用Express,用Node.js构建网络服务器。
-
Nodemon是观察文件变化并自动重启node进程的一个好方法。
-
环境变量可以存储一个应用程序的各种设置。要读取环境变量,使用process.env。
-
永远不要相信客户端发送的数据。使用Joi来执行输入验证。
对Express和REST有用的片段
// Creating a web server
const express = require('express');
const app = express();
// Creating a resource
app.post('/api/resources', (req, res) => {
// Create the resource and return the resource object
resn.send(resource);
});
// Getting all the resources
app.get('/api/resources', (req, res) => {
// To read query string parameters (?sortBy=title)
const sortBy = req.query.sortBy;
// Return the resources
res.send(resources);
});
// Getting a single resource
app.get('/api/resources/:id', (req, res) => {
const resourceId = req.params.id;
// Lookup the resource and if not found, return 404
res.status(404).send('Resource not found.');
// Else, return the resource object
res.send(resource);
});
// Updating a resource
app.put('/api/resources/:id', (req, res) => {
// If resource not found, return 404, otherwise update it
// and return the updated object.
});
// Deleting a resource
app.delete('/api/resources/:id', (req, res) => {
// If resource not found, return 404, otherwise delete it
// and return the deleted object.
});
// Listen on port 4000
app.listen(4000, () => console.log('Listening…'));
// Reading the port from an environment variable
const port = process.env.PORT || 5000;
app.listen(port);