如何使用 NodeJS 构建 REST API
API 是什么?🤔
设置项目
构建 REST API
在 Heroku 上部署我们的 API
👋 大家好,我知道我已经很久没发新博客了😅。👀 所以在这篇博文中,我们将构建一个 REST API,为使用 NodeJS 和 MongoDB 的开发者提供动力。那就让我们开始吧🏄♂️
API 是什么?🤔
API 代表“应用程序编程接口”,它是一种允许两个应用程序相互通信的工具📞。让我们通过一些实际的例子来理解 API 的含义✨
假设您构建了一个出色的电子商店应用程序,并希望其他开发者能够在此基础上构建应用程序。现在,您需要构建某种软件来在您的 Web 服务和开发者的应用程序之间进行通信,而这正是 API 发挥作用的地方。
什么是 REST API?🤔
现在,我们来谈谈“REST API”。REST 代表表述性状态转移 (Representational State Transfer),它是最广为人知的 API 架构类型之一。这类 API 遵循客户端-服务器模型,其中一个程序发送请求,另一个程序返回一些数据。
请求采用 HTTP 方法,例如 POST、GET、PUT、DELETE……
当我们构建项目时,你会对 API 和 REST API 有更清晰的理解👀。还在等什么,让我们开始深入编码吧👨💻。
设置项目
让我们设置我们的项目,以便我们可以开始编码👨💻。
- 为我们的项目创建一个单独的文件夹
$ mkdir dev-credits-api
- 导航到文件夹
$ cd dev-credits-api
- 初始化项目
$ npm init
- 安装所需的软件包
$ npm install mongoose express dotenv cors
# or
$ yarn add mongoose express dotenv cors
- Express 是我们使用 REST API 的框架
-
Mongoose 是我们将用来与 MongoDB 数据库进行通信的工具
4.1. 安装 nodemon 作为开发依赖项
$ npm install nodemon -D # or $ yarn add nodemon -D
- Nodemon 用于在检测到目录中的文件更改时自动重启服务器。这很有用,因为我们不必每次更改文件时都重启服务器。
构建 REST API
由于我们已经完成了项目的设置,让我们开始构建 REST API。
创建一个名为index.js
以下是基本 Express 应用程序的样板代码
index.js
const express = require('express');
const app = express();
const port = process.env.PORT || 3000;
app.listen(port, async () => {
console.log(`Server is running at port ${port}`);
});
让我们将其分解并理解每个部分:
- 我们需要将 express 包放入我们的文件中,以便我们可以使用它
- 我们正在为变量 port 赋值,也就是服务器运行的端口。你可能会想为什么会有这个值
process.env.PORT
?🤔。这是因为在 Heroku 等服务上部署时,端口号可能会发生变化,可能不是 3000,所以我们告诉它,如果存在 PORT 环境变量,则使用该值,否则使用 3000。 - 最后一段代码告诉服务器应该监听哪个端口,在我们的例子中它是
PORT
变量
让我们添加一个名为 的文件start
的新脚本package.json
,该脚本使用 nodemon 在检测到文件更改时自动重启服务器。更改后,我们的脚本package.json
将如下所示:
"scripts": {
"start": "nodemon index.js"
}
让我们通过运行以下npm start
命令来启动服务器。服务器将运行在http://localhost:3000 上。你会看到类似这样的错误提示:
发生这种情况是因为我们没有定义/
(即根路由)
HTTP 方法解释
让我们暂时放下编码,了解一下它们的作用以及成功和错误状态,以便于调试😎
得到
作用:从指定资源请求数据
成功响应:200 OK
错误响应:404 未找到
邮政
作用:向服务器发送数据以创建新资源
成功响应:201已创建
错误响应:404 未找到或 409 冲突 - 如果资源已存在
放
作用:向服务器发送数据以更新现有资源
成功响应:200 OK
错误响应:204 无内容、404 未找到或 405 方法不允许
删除
作用:从服务器删除资源
成功响应:200 OK
错误响应:404 未找到或 405 方法不允许
查看http.cat,通过有趣的猫咪图片了解每个 HTTP 状态代码的含义 😹
添加路线🛣
路由是 Express 应用的不同 URL 路径,与不同的 HTTP 方法相关联,例如GET
、POST
、DELETE
、PUT
。
让我们开始创建/
发送“Hello, World!”的代码。
port
在我们声明变量的行上方添加下面的代码
index.js
app.get('/', function (req, res) {
res.send('Hello, World!');
});
让我们分解一下这段代码:
- 该
get
方法指定该路由的 HTTP 方法。您可以使用其他 HTTP 方法post
,例如delete
……- 有一种特殊的路由方法
all
,用于处理各种 HTTP 方法的路由
- 有一种特殊的路由方法
- 当服务器从该端点接收到具有指定 HTTP 方法的请求时,会调用一个回调方法
🥳 好棒!“Hello, World”现在在/
路线中可见了
设置 MongoDB
现在让我们进入 MongoDB 数据库😎。
前往MongoDB并注册/登录并创建一个新项目
如果您愿意的话,您可以让您的同事参与该项目。
创建项目后,点击Build a Database
您将看到如下屏幕:
让我们继续选择免费计划👀
您将看到有关云提供商和位置的更多选项
让我们选择最近的区域并继续前进。
系统会要求您创建一个用户。这是必需的,因为您需要用户名和密码来生成连接 URL,该 URL 随后将用于将 MongoDB 连接到您的 NodeJS 应用程序。
创建集群需要 1 到 3 分钟。所以,在那之前,我们先喝杯咖啡吧☕。啊……创建成功了,我们回去写代码吧👨💻
点击Connect
点击Connect your application
复制连接 URL
创建一个.env
文件并替换<password>
为您之前替换的用户密码
MONGODB_URL="mongodb+srv://kira272921:<password>@dev-credits-api.t5tkf.mongodb.net/myFirstDatabase?retryWrites=true&w=majority"
让我们回到旧的index.js
文件
将 Express 应用程序连接到 MongoDB
让我们首先mongoose
要求dotenv
const mongoose = require('mongoose');
const dotenv = require('dotenv');
让我们也配置一下 dotenv
dotenv.config();
最后,让我们添加将 Express 应用程序连接到 MongoDB 的代码片段
mongoose
.connect(process.env.MONGODB_URL, {
useNewUrlParser: true,
useUnifiedTopology: true,
})
.then(() => {
console.log('Connected to MongoDB');
})
.catch((err) => {
console.log(err);
});
文件index.js
显示现在看起来像这样
index.js
const express = require('express');
const mongoose = require('mongoose');
const dotenv = require('dotenv');
dotenv.config();
const app = express();
mongoose
.connect(process.env.MONGODB_URL, {
useNewUrlParser: true,
useUnifiedTopology: true,
})
.then(() => {
console.log('Connected to MongoDB');
})
.catch((err) => {
console.log(err);
});
app.get('/', function (req, res) {
res.send('Hello, World!');
});
const port = process.env.PORT || 3000;
app.listen(port, async () => {
console.log(`Server is running at port ${port}`);
});
🥳 我们已成功将 Express 应用程序连接到 MongoDB 数据库。
创建模式和模型📝
Schema 是我们数据库中文档的结构。它说明了哪些字段是必需的,以及每个字段的数据类型是什么。
模型提供了与数据库交互(读取、插入、更新等)的编程接口。
让我们创建一个名为的新文件夹model
,并在其中创建一个model.js
我们将定义架构的地方
model/model.js
const mongoose = require('mongoose');
const devCredits = new mongoose.Schema({
credits: {
type: Number,
required: true,
},
id: {
type: Number,
required: true,
},
});
module.exports = mongoose.model('devCredits', devCredits);
让我们分解一下并理解
- 我们将
mongoose
包导入到model/model.js
文件中 - 我们创建了一个名为 的新模式
devCredits
。该结构包含credits
和id
。Credits 是该人拥有的开发者积分数量,id 是该用户的 Discord ID(此 API 最初是为 Discord 机器人Dev credits bot创建的,因此数据库的模式有点基于 Discord 🤷♂️) - 我们最终创建了一个名为“devCredits”的模型
添加更多功能😎
让我们为 REST API 添加更多路由。让我们添加一些路由,以便我们可以通过用户的 Discord ID 获取其总开发者积分,并使用其他路由将开发者积分授予其他用户。
将开发者荣誉授予其他开发者
让我们将刚刚创建的模型导入到index.js
文件中。
const devCredits = require('./model/model.js');
让我们在index.js
文件中添加一个新的 POST 路由
app.post('/post', function (req, res) {
const credit = new devCredits({
id: req.body.id,
credits: req.body.credits,
});
devCredits.countDocuments({ id: req.body.id }, function (err, count) {
if (count > 0) {
devCredits.findOneAndUpdate(
{ id: req.body.id },
{
$inc: {
credits: req.body.credits,
},
},
{ new: true },
(err, devCredit) => {
if (err) {
res.send(err);
} else res.json(devCredit);
}
);
} else {
credit.save((err, credits) => {
if (err) {
res.send(err);
}
res.json(credits);
});
}
});
});
让我们了解一下到底发生了什么:
- 我们创建了一个新的 POST 路由(
/post
) - 我们使用模型验证从客户端收到的数据
- 在下一段代码中,我们检查用户(用户 ID)是否已存在于数据库中
- 如果存在,我们将增加信用值
- 否则,我们将创建一个包含用户 ID 的新文档并添加积分
如何测试 API?
我们已经成功在 API 中添加了一项新功能 🥳。不过,先别急,我们该如何测试它呢 🤔
👀 我们将使用一个名为Thunder Client的 VSCode 扩展程序,用于 API 测试。赶紧下载它,并在 API 中测试一下我们的新功能吧🥳。
下载完成后,您将在侧边栏中看到一个雷电图标👀
点击雷霆图标,你会看到类似这样的部分
点击New Request
。系统将提示您进行如下操作
现在让我们测试一下我们的/post
路线🥳。将输入框中的 URL 从 更改https://www.thunderclient.com/welcome
为HTTP:localhost:3000/post
将 HTTP 方法从 更改GET
为POST
导航到Body
选项卡,这是我们要编写请求正文的部分。
我已经添加了我的 discord ID 并给了它 100 个开发者积分,因为为什么不呢
让我们点击并希望它能起作用🤞
🥁🥁🥁🥁🥁 我们收到一个错误
发生这种情况是因为我们没有任何中间件,所以让我们快速
index.js
app.use(cors());
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
注意:我们已经将 cors 安装为单独的包,因此不要忘记导入它
现在让我们再试一次,以便它现在可以工作了🤞
🎉太棒了!我们已经成功创建了 API 中第一个与 MongoDB 数据库交互的功能。
获取用户的总开发积分
让我们将刚刚创建的模型导入到index.js
文件中。
const devCredits = require('./model/model.js');
让我们在index.js
文件中添加一条新路线
app.get('/get/:id', function (req, res) {
devCredits.find({ id: req.params.id }, { _id: 0, __v: 0 }, (err, data) => {
if (err) {
res.json(err);
}
res.json(data);
});
});
让我们来分析一下
- 我们用 GET 方法创建了一条新路由
- 我们正在数据库中查找参数中给出的 ID
让我们使用 Thunder Client 👀 再次测试一下。
🎉太棒了!
清理代码库
让我们稍微清理一下代码库😅。
让我们创建一个名为的新文件夹routes
,并在其中创建一个router.js
包含路线的新文件
routes/router.js
const router = require('express').Router();
const devCredits = require('../model/model.js');
router.get('/get/:id', function (req, res) {
devCredits.find({ id: req.params.id }, { _id: 0, __v: 0 }, (err, data) => {
if (err) {
res.json(err);
}
res.json(data);
});
});
router.post('/post', function (req, res) {
const credit = new devCredits({
id: req.body.id,
credits: req.body.credits,
});
devCredits.countDocuments({ id: req.body.id }, function (err, count) {
if (count > 0) {
devCredits.findOneAndUpdate(
{ id: req.body.id },
{
$inc: {
credits: req.body.credits,
},
},
{ new: true },
(err, devCredit) => {
if (err) {
res.send(err);
} else res.json(devCredit);
}
);
} else {
credit.save((err, credits) => {
if (err) {
res.send(err);
}
res.json(credits);
});
}
});
});
module.exports = router;
我们已经将routes/router.js
文件导入到文件中index.js
并使用了
index.js
const express = require('express');
const mongoose = require('mongoose');
const dotenv = require('dotenv');
const cors = require('cors');
dotenv.config();
const router = require('./routes/router.js');
const app = express();
app.use(cors());
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
mongoose
.connect(process.env.MONGODB_URL, {
useNewUrlParser: true,
useUnifiedTopology: true,
})
.then(() => {
console.log('Connected to MongoDB');
})
.catch((err) => {
console.log(err);
});
app.get('/', function (req, res) {
res.send('Hello, World!');
});
app.use(router);
const port = process.env.PORT || 3000;
app.listen(port, async () => {
console.log(`Server is running at port ${port}`);
});
让我们测试一下,以确保我们的代码和我们没有通过清理混乱而搞砸。
🥳 太棒了!没有任何错误,代码仍然像以前一样工作。
😅 看起来是不是routes/router.js
有点缺乏逻辑,有点混乱?
让我们创建一个名为 的新文件夹controllers
。在这个文件夹中,我们将存储与每个路由相关的逻辑。
让我们首先在controllers
名为getCredits.js
和的文件夹中创建一个新文件,其中包含与路由和路由postCredits.js
相关的逻辑/get
/post
controllers/getCredits.js
const devCredits = require('../model/model.js');
const getCredits = (req, res) => {
devCredits.find({ id: req.params.id }, { _id: 0, __v: 0 }, (err, data) => {
if (err) {
res.json(err);
}
res.json(data);
});
};
module.exports = getCredits;
controllers/postCredits.js
const devCredits = require('../model/model.js');
const postCredits = (req, res) => {
const credit = new devCredits({
id: req.body.id,
credits: req.body.credits,
});
devCredits.countDocuments({ id: req.body.id }, function (err, count) {
if (count > 0) {
devCredits.findOneAndUpdate(
{ id: req.body.id },
{
$inc: {
credits: req.body.credits,
},
},
{ new: true },
(err, devCredit) => {
if (err) {
res.send(err);
} else res.json(devCredit);
}
);
} else {
credit.save((err, image) => {
if (err) {
res.send(err);
}
res.json(image);
});
}
});
};
module.exports = postCredits;
routes/router.js
const router = require('express').Router();
const devCredits = require('../model/model.js');
const getCredits = require('../controllers/getCredits.js');
const postCredits = require('../controllers/postCredits.js');
router.get('/get/:id', getCredits);
router.post('/post', postCredits);
module.exports = router;
呼,这可真是费了不少功夫啊😹
添加速率限制
你肯定不希望某个随机的家伙把你的整个数据库都塞满垃圾邮件吧?所以,让我们给 API 添加一个速率限制,限制客户端每 x 分钟只能执行几个请求。
让我们安装express-rate-limit包
$ npm install express-rate-limit
# or
$ yarn add express-rate-limit
让我们创建一个文件夹,包含我们 API 的所有中间件。在该文件夹下middleware
创建一个名为rateLimiter.js
middleware
middleware/rateLimiter.js
const rateLimit = require('express-rate-limit');
const rateLimiter = rateLimit({
windowMs: 1 * 60 * 1000, // 1 minute
max: 10,
message: 'Bonk 🔨',
});
module.exports = rateLimiter;
我们来了解一下这段代码是做什么的?
- 我们正在导入
express-rate-limit
包 - 指定
windowMs
持续时间 - 指定
max
客户端在指定时间内可以发出的最大请求数量 - 这
message
是当客户端超出最大限制时显示的消息
让我们导入index.js
文件并测试一下
index.js
const rateLimiter = require('./middleware/rateLimiter.js');
app.use(rateLimiter);
😹我被自己搞砸了
在 Heroku 上部署我们的 API
👀 我们已经成功构建了一个 API,但如果不部署它,其他开发人员如何使用它?
让我们将其部署到 Heroku 🚀 上。
首先在目录中初始化一个 git 仓库。创建一个新的 GitHub 仓库,并将你的更改推送到该仓库 👀
让我们创建一个名为 的新文件Procfile
,它只是告诉 Heroku 需要运行哪个命令。将以下内容添加到Procfile
文件中
web: node index.js
注意:nodemon 在生产阶段不起作用。它只在开发阶段起作用,所以我们必须使用老方法node index.js
在Heroku上创建一个帐户并点击Create new app
,给你的 API 起一些很酷的名字
前往设置选项卡并点击Reveal Config Vars
添加一个新的配置变量,其键为 as MONGODB_URL
,值作为 MongoDB 连接 URL
返回部署选项卡并将之前创建的 GitHub 存储库连接到 Heroku 应用程序
点击Deploy branch
按钮。TADA 🚀您已成功创建 REST API 并进行了部署 :D
本教程的完整源代码将在我的 GitHub 上提供https://github.com/Kira272921/dev-credits-api
查看我们今天构建的 API:
https://devcredits-api.herokuapp.com/
这篇博客就到这里啦🤞。下篇博文再见。
文章来源:https://dev.to/byteslash/how-to-build-a-rest-api-using-nodejs-19ai