在本教程中,我们将使用 Node.jsExpress 构建一个 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 是费用类别的名称。我们将提供一个静态的类别列表供选择:travelfoodaccomodationfun

当我们想要检索旅行费用时,我们使用 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")
  }
)

在这里,我们也可以得到对 tripsexpenses 集合的引用:

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,它表示我们如何称呼我们的旅行。例如 瑞典 2018Yosemite 八月 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) => {})
})

然后我们可以处理 erritems 结果:

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 它是来自 travelfoodaccomodationfun 中的一个的名称
  • 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 屏幕截图,显示了它的工作方式: