Node.js Express Rest Api的基础教程

145 阅读9分钟

让我们看看如何使用Express建立一个简单的REST api来实现所有的CRUD操作。REST是指Representational State Transfer。它是一种通过客户端和服务器架构建立HTTP服务的惯例。在REST中,HTTP协议被用来促进服务器上资源的创建、读取、更新和删除。这些操作可以统称为CRUD操作。每个操作都使用不同的HTTP动词。创建使用POST请求。读取使用的是GET请求。更新使用的是PUT请求。最后,删除使用DELETE请求。在本教程中,我们将使用Node.jsExpress创建所有这些服务。


Express网络服务器

我们已经学会了如何构建一个Express项目,所以让我们继续创建一个新的项目,以便我们可以建立新的REST api。输入以下命令开始吧。

  • node $mkdir express-rest
  • node $cd express-rest
  • express-rest $npm init --yes

express rest demo

我们还需要在项目的根部有一个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 来启动网络服务器。
listening on port 3000

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

现在我们将添加一个新的路由,模拟一个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,看一看吧
array of games get request


Nodemon Express

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

你将不再需要停止并重新启动服务器。继续进行你的更新,你会看到命令行自动反映了这一点。
nodemon restarting due to changes

当然,浏览器中的结果也会更新。
index js got updated


用环境变量设置端口

到目前为止,我们一直在为不同的应用程序变量(如监听端口)进行硬编码。一旦你建立了比教程程序更大的程序,你就不会想这么做了。处理这个问题的更合适的方法是检查一个名为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。
export env port node


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,注意我们只是在浏览器中显示路由参数的值。
route params id

多参数
你可以在url中拥有一个以上的路由参数。考虑一下这段代码。

app.get('/api/games/:title/:publisher', (req, res) => {
    res.send(req.params);
});

现在,如果我们访[问http://localhost:4000/api/games/mario/nintendo,这里是我们得到的结果。
multiple route params express

查询参数
我们也可以在url中访问查询参数。在这里,我们更新代码,允许路由参数和查询参数。

app.get('/api/games/:title/:publisher', (req, res) => {
    res.send([req.params, req.query]);
});

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);
});

看起来不错!
fetch all games get request

现在我们想只用一个路由参数来找到一个特定的游戏。为此,我们可以使用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 ,我们得到的是大金刚游戏。
express find by id


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的游戏。
postman post request

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

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


Joi输入验证

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

一旦安装了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发送一个错误的请求,看看结果。
send bad 400 request

我们得到的响应是一个错误,就像我们所期望的那样。

{
    "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的游戏是 "大金刚"。我们将尝试把它改为 "洞穴故事"。
put request sent for update

我们得到的回复与我们期望的一样。
put request 200 ok

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


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的游戏。
postman delete request

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

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


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);