在本教程中,我们将使用 Node.js 和 Express 构建一个 REST API。
该 API 将提供一组 GET 和 POST 端点,以允许获取数据和发送数据。
我们将使用 MongoDB 数据库存储这些数据。
提示:在继续本教程之前,请确保在您的系统上安装了 MongoDB 数据库(或者您可以使用 Cloud MongoDB 数据库,如果您更喜欢这种方式)
我们的任务是创建一个旅行成本计算器应用程序。
想象一下去旅行,你有一个应用程序(可以是渐进式 Web 应用程序,也可以是移动应用程序),你可以在其中添加你的任何费用。汽油、酒店、食物、门票等等。
当旅行结束时,你可以将其归档,并成为历史的一部分-你可以导航并查看过去旅行花费了多少钱。
我们这里不会创建应用程序的前端,只有 API。
现在让我们详细分解这个问题,并将其转化为一系列 API 端点。
端点是我们将调用以进行操作的唯一 URL。
比如,添加一个新的带有其名称的旅行。
在开始时,没有存储的旅行,我们需要添加一个。我想象应用程序会要求用户提供名称,并有一个“创建旅行”按钮。当点击时,应用程序会将名称发送给 /trip
端点,并使用 POST
HTTP 方法。
我们有了第一个端点,它将接受一个 name
属性。
POST /trip { name }
另一个端点将列出旅行,它是:
GET /trips
默认情况下,它将按照创建日期排序返回旅行。
当用户想要添加新的费用时,应用程序会使用 POST 方法调用 /expense
端点,并提供描述费用的一些参数。
POST /expense { trip, date, amount, category, description }
trip
是当前选择的旅行的 ID。
category
是费用类别的名称。我们将提供一个静态的类别列表供选择:travel
、food
、accomodation
、fun
。
当我们想要检索旅行费用时,我们使用 GET 方法调用 /expenses
端点:
GET /expenses { trip }
传递 trip
标识符。
开始项目
我将使用本地安装的 Node.js。
我们通过进入一个新的文件夹(命名为tripcost
),然后输入命令 npm init -y
来开始我们的 Node.js 项目。
我们将使用 MongoDB 作为我们的数据库。
使用以下命令安装 mongodb
Node.js 包:
npm install mongodb
同时,在此处还要安装 Express:
npm install express
现在创建一个 server.js
文件,我们将在其中存储我们的 API 代码,并开始引入 Express 和 MongoDB:
const express = require("express")
const mongo = require("mongodb").MongoClient
初始化 Express 应用:
const app = express()
现在,我们可以加入我们支持的 API 端点的存根:
app.post("/trip", (req, res) => {
/* */
})
app.get("/trips", (req, res) => {
/* */
})
app.post("/expense", (req, res) => {
/* */
})
app.get("/expenses", (req, res) => {
/* */
})
最后,使用 app
上的 listen()
方法启动服务器:
app.listen(3000, () => console.log("Server ready"))
您可以在项目文件夹中使用 node server.js
运行应用程序。
添加旅行
我们提供客户端一个通过 POST /trip
端点添加旅行的方式:
app.post("/trip", (req, res) => {
/* */
})
让我们继续实现它。
我们已经包括了 MongoDB 库,所以我们可以在我们的端点实现中使用它:
const mongo = require("mongodb").MongoClient
接下来,我们构建 MongoDB 服务器的 URL。如果您在本地运行项目,并且 MongoDB 也在本地运行,则 URL 可能是这样的:
const url = "mongodb://localhost:27017"
因为 27017
是默认端口。
接下来,让我们使用 connect()
连接到数据库:
let db
mongo.connect(
url,
{
useNewUrlParser: true,
useUnifiedTopology: true,
},
(err, client) => {
if (err) {
console.error(err)
return
}
db = client.db("tripcost")
}
)
在这里,我们也可以得到对 trips 和 expenses 集合的引用:
let db, trips, expenses
mongo.connect(
url,
{
useNewUrlParser: true,
useUnifiedTopology: true,
},
(err, client) => {
if (err) {
console.error(err)
return
}
db = client.db("tripcost")
trips = db.collection("trips")
expenses = db.collection("expenses")
}
)
现在我们可以回到我们的端点。
这个端点需要一个参数 name
,它表示我们如何称呼我们的旅行。例如 瑞典 2018 或 Yosemite 八月 2018。
我们期望以 JSON 格式的数据,使用 Content-Type: application/json
,所以我们需要使用 express.json()
中间件:
app.use(express.json())
我们现在可以通过从 请求.body
中引用它来访问数据:
app.post("/trip", (req, res) => {
const name = req.body.name
})
一旦我们有了名字,我们就可以使用 trips.insertOne()
方法将该旅行添加到数据库中:
app.post("/trip", (req, res) => {
const name = req.body.name
trips.insertOne({ name: name }, (err, result) => {})
})
我们处理错误,如果存在于 err
变量中,否则我们向客户端发送一个 200 响应(成功),在 JSON 响应中添加一个 ok: true
消息:
app.post("/trip", (req, res) => {
const name = req.body.name
trips.insertOne({ name: name }, (err, result) => {
if (err) {
console.error(err)
res.status(500).json({ err: err })
return
}
console.log(result)
res.status(200).json({ ok: true })
})
})
就是这样!
现在,通过按 Ctrl+C
停止 Node 应用程序,并再次运行它来重新启动。
您可以使用 Insomnia 应用程序 来测试此端点,Insomnia 是测试和与 REST 端点交互的好方法:
列出旅行
旅行列表由 GET /trips
端点返回。它不接受参数:
app.get("/trips", (req, res) => {
/* */
})
我们已经初始化了 trips
集合,所以我们可以直接访问它来获取该列表。
我们使用 trips.find()
方法,必须使用 toArray()
将其结果转换为数组:
app.get("/trips", (req, res) => {
trips.find().toArray((err, items) => {})
})
然后我们可以处理 err
和 items
结果:
app.get("/trips", (req, res) => {
trips.find().toArray((err, items) => {
if (err) {
console.error(err)
res.status(500).json({ err: err })
return
}
res.status(200).json({ trips: items })
})
})
这是 Insomnia 调用 API 的结果:
添加费用
我们之前获得了旅行列表。每个旅行都有一个关联的 _id
属性,MongoDB 在添加时会自动添加该属性:
{
"trips": [
{
"_id": "5bdf03aed64fb0cd04e15728",
"name": "Yellowstone 2018"
},
{
"_id": "5bdf03c212d45cdb5ccec636",
"name": "Sweden 2017"
},
{
"_id": "5bdf047ccf4f42dc368590f6",
"name": "First trip"
}
]
}
我们将使用这个 _id
来注册一个新的费用。
如果您记得的话,添加新的费用的端点是这样的:
POST /expense { trip, date, amount, category, description }
此时的 trip
将是我们之前注册的一次旅行的 _id
。想象一下,在应用程序中,用户将添加一次旅行,这将保持当前的旅行状态,直到添加(或选择)新的旅行。
让我们继续实现我们的存根:
app.post("/expense", (req, res) => {
/* */
})
与添加旅行时一样,这次我们将使用 insertOne()
方法,但这次是在 expenses
集合上。
我们从请求体中获取 5 个参数:
trip
date
日期,使用 ISO 8601 格式(例如2018-07-22T07:22:13
),在 GMT 时区amount
金额category
它是来自travel
、food
、accomodation
、fun
中的一个的名称description
费用的描述,以便我们以后记得它
app.post("/expense", (req, res) => {
expenses.insertOne(
{
trip: req.body.trip,
date: req.body.date,
amount: req.body.amount,
category: req.body.category,
description: req.body.description,
},
(err, result) => {
if (err) {
console.error(err)
res.status(500).json({ err: err })
return
}
res.status(200).json({ ok: true })
}
)
})
列出所有费用
我们谜题的最后一步是获取费用。
我们需要填写 /expenses
端点的存根,这是最后一个缺失的端点:
app.get("/expenses", (req, res) => {
/* */
})
这个端点接受 trip
参数,它是数据库中存储的一次旅行的 _id
属性。
app.get("/expenses", (req, res) => {
expenses.find({ trip: req.body.trip }).toArray((err, items) => {
if (err) {
console.error(err)
res.status(500).json({ err: err })
return
}
res.status(200).json({ expenses: items })
})
})
以下是 Insomnia 屏幕截图,显示了它的工作方式: