使用Node.js、Express和MongoDB建立一个REST API的详细指南

412 阅读7分钟

在本教程中,我们将使用Node.jsExpress创建一个REST API。

这个API将暴露一组GET和POST端点,以允许获取数据,并发送数据进来。

我们将使用MongoDB数据库来存储这些数据。

提示:在继续学习本教程之前,确保你在系统中安装了MongoDB数据库(如果你愿意,也可以使用云MongoDB数据库)。

我们的任务是创建一个旅行费用计算器应用程序。

想象一下,在一次旅行中,你有你的应用程序(可以是一个渐进式的Web应用程序,或者一个移动应用程序,例如),在那里你可以添加你的任何费用。汽油、酒店、食物、门票等等。

当旅行结束时,你把它归档,成为历史的一部分--你可以浏览并看到你在过去的旅行中花了多少钱。

我们不打算在这里创建应用程序的前端,只是创建API。

现在让我们把它分解成细节,并把它翻译成一系列的API端点。

一个端点是一个唯一的URL,我们将调用它来创建一个操作。

就像添加一个新的行程,有它的名字。

在开始的时候,并没有存储任何行程,我们需要添加一个。我想象应用程序将询问用户的名字,并且会有一个 "创建旅行 "的按钮。当点击时,应用程序将把名字发送给我们,用POST HTTP方法发送到/trip 端点。

我们有了第一个端点,它将接受一个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的本地安装。

我们开始我们的Node.js项目,进入一个新的文件夹(称之为tripcost )并输入命令npm init -y

flaviocopes.com/rest-api-ex…

flaviocopes.com/rest-api-ex…

我们将使用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")
  }
)

而当我们在这里的时候,让我们也得到一个对旅行费用集合的引用。

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

现在我们可以回到我们的端点。

这个端点希望有1个参数,name ,它代表我们如何调用我们的行程。例如,瑞典2018年优胜美地2018年8月

请看我的教程,如何使用Express检索POST查询参数

我们期望数据以JSON形式出现,使用Content-Type: application/json ,所以我们需要使用express.json() 中间件。

app.use(express.json())

现在我们可以通过引用Request.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) => {})
})

如果你得到一个类似 "无法读取未定义的属性insertOne "的错误,确保tripsmongo.connect() 中成功设置。在调用insertOne()之前添加一个console.log(trips)以确保它包含集合对象。

我们处理错误,如果在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应用程序来测试这个端点,这是一个测试和互动REST端点的好方法。

flaviocopes.com/rest-api-ex…

列出行程

行程列表是由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调用的结果。

flaviocopes.com/rest-api-ex…

增加一项开支

我们之前得到了旅行的列表。每个行程都有一个相关的_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格式(例如: ),GMT时区2018-07-22T07:22:13

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

flaviocopes.com/rest-api-ex…

列出所有费用

拼图的最后一块是获取费用。

我们需要填写/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的截图显示了它的工作情况。

image.png flaviocopes.com/rest-api-ex…