使用 Express 和 Mongoose 构建 REST API

2025-06-10

使用 Express 和 Mongoose 构建 REST API

这篇文章最初发表在这里

本教程将指导您使用 Node.js、Express 和 Mongoose 构建具有 CRUD 功能的 RESTful API。我希望您具备 Node.js 和 JavaScript 的基础知识。如果您具备这些知识,那么您就可以开始了!

先决条件

首先需要在您的机器上安装这些软件:

入门

启动这个项目,我们唯一需要的就是一个已经初始化了 npm 包的空白文件夹。那就让我们创建一个吧!

$ mkdir learn-express
$ cd learn-express
$ npm init -y

现在,让我们安装一些有用的包。

$ npm install express mongoose body-parser

在这里,我们安装Express作为我们的 Web 框架,安装 mongoose与我们的 MongoDB 数据库交互,安装body-parser来解析我们的请求主体。

我还在我的 GitHub 上发布了整个项目的源代码。请将其克隆到您的计算机中。

$ git clone https://github.com/rahmanfadhil/learn-express-mongoose

基本 Express 服务器

我们现在可以开始创建index.js并建立一个简单的 Express 服务器。

const express = require("express")

const app = express()

app.listen(5000, () => {
  console.log("Server has started!")
})

我们首先导入express刚刚安装的包。然后,创建一个新的 express 实例并将其放入app变量中。这个app变量让我们可以完成配置 REST API 所需的一切,例如注册路由、安装必要的中间件等等。

尝试通过运行以下命令来运行我们的服务器。

$ node index.js
Server has started!

或者,我们可以设置一个新的 npm 脚本来使我们的工作流程更加简单。

{
  "scripts": {
    "start": "node index.js"
  }
}

然后,我们可以通过执行来运行我们的服务器npm start

$ npm start
Server has started!

设置 Mongoose

Mongoose 是 Node.js 最受欢迎的 MongoDB 包装器。它使我们能够轻松地与 MongoDB 数据库进行交互。我们可以开始将服务器连接到 MongoDB 数据库了。

const express = require("express")
const mongoose = require("mongoose") // new

// Connect to MongoDB database
mongoose
  .connect("mongodb://localhost:27017/acmedb", { useNewUrlParser: true })
  .then(() => {
    const app = express()

    app.listen(5000, () => {
      console.log("Server has started!")
    })
  })

这里,我们导入了mongoose包,并用它来连接到名为 的数据库acmedb,不过你可以随意命名。如果你还没有创建该数据库,不用担心,mongoose 会帮你创建。

connect 方法返回一个承诺,因此我们可以等到它解决,然后运行我们的 Express 服务器。

再次运行服务器,确保没有错误。

$ npm start
Server has started!

现在,我们已经成功将服务器与数据库连接起来,现在是时候创建我们的第一个模型了。

猫鼬模型

在 NoSQL 世界中,每个数据都存储在一个文档中。多个相同类型的文档可以放在一个集合中。

模型是一个类,它让我们与数据库的特定集合进行交互。

定义模型还需要定义模式。模式基本上告诉模型我们的文档应该是什么样子。即使在 NoSQL 世界中,文档模式非常灵活,但 Mongoose 可以帮助我们保持数据更加一致。

假设我们有一个博客 API。那么,我们显然会创建一个Post模型。这个帖子模型有一个 schema,其中包含可以添加到单个文档中的字段。在本例中,我们只使用一个titleandcontent字段。

因此,让我们在项目中添加一个名为的新文件夹,并在其中models创建一个名为的文件。Post.js

const mongoose = require("mongoose")

const schema = mongoose.Schema({
  title: String,
  content: String
})

module.exports = mongoose.model("Post", schema)

在这里,我们使用 构建一个模式mongoose.Schema,并定义字段和数据类型。然后,我们基于mongoose.model刚刚创建的模式,使用 创建一个新模型。

获取所有帖子

在这里,我们可以创建一个名为的新文件routes.js,其中将包含我们的 Express 路线。

const express = require("express")
const router = express.Router()

module.exports = router

我们还需要导入express,但这次我们想使用express.Router。它允许我们注册路由并在我们的应用程序中使用它(在index.js)。

现在,我们准备在 Express 中创建第一条真正可以执行某些操作的路线!

让我们创建一个可以获取现有帖子列表的路线。

const express = require("express")
const Post = require("./models/Post") // new
const router = express.Router()

// Get all posts
router.get("/posts", async (req, res) => {
  const posts = await Post.find()
  res.send(posts)
})

module.exports = router

在这里,我们导入Post模型并创建一个GET带有方法的新路由router.get。此方法将接受路由的端点,以及路由处理程序来定义应发送到客户端的数据。在本例中,我们将从find模型中获取所有帖子,并使用方法发送结果res.send

由于从数据库获取文档是异步的,我们需要使用await来等待操作完成。所以,别忘了将你的函数标记为async。这样,在数据完全获取后,我们就可以将其发送到客户端。

现在,我们可以在我们的中安装我们的路线index.js

const express = require("express")
const mongoose = require("mongoose")
const routes = require("./routes") // new

mongoose
  .connect("mongodb://localhost:27017/acmedb", { useNewUrlParser: true })
  .then(() => {
    const app = express()
    app.use("/api", routes) // new

    app.listen(5000, () => {
      console.log("Server has started!")
    })
  })

首先,我们导入./routes.js文件来获取所有的路由,并用app.use为前缀的方法注册它/api,这样,我们所有的帖子都可以在 中访问/api/posts

尝试运行我们的服务器并获取/api/posts,让我们看看我们得到了什么。

$ curl http://localhost:5000/api/posts
[]

现在,我们从服务器获取了一个空数组。这是因为我们还没有创建任何帖子。那么,为什么不创建一个呢?

创建帖子

要创建帖子,我们需要接受POST来自的请求/api/posts

// ...

router.post("/posts", async (req, res) => {
  const post = new Post({
    title: req.body.title,
    content: req.body.content
  })
  await post.save()
  res.send(post)
})

这里,我们创建一个新Post对象,并从属性中填充字段req.body。该req对象包含客户端请求数据,而请求体就是其中之一。

然后,我们还需要使用该方法保存记录save。保存数据也是异步的,因此我们需要使用 async/await 语法。

默认情况下,Express 不知道如何读取请求体。这就是为什么我们需要使用body-parser将请求体解析为 JavaScript 对象。

const express = require("express")
const mongoose = require("mongoose")
const routes = require("./routes")
const bodyParser = require("body-parser") // new

mongoose
  .connect("mongodb://localhost:27017/acmedb", { useNewUrlParser: true })
  .then(() => {
    const app = express()
    app.use("/api", routes)
    app.use(bodyParser.json()) // new

    app.listen(5000, () => {
      console.log("Server has started!")
    })
  })

在这里,我们使用body-parser库作为中间件来解析 JSON 主体,以便我们可以通过req.body路由处理程序访问它。

让我们测试一下我们刚刚创建的创建帖子功能!

$ curl http://localhost:5000/api/posts \
    -X POST \
    -H "Content-Type: application/json" \
    -d '{"title":"Post 1", "content":"Lorem ipsum"}'
{
    "_v": 0,
    "_id": <OBJECT_ID>,
    "title": "Post 1",
    "content": "Lorem ipsum"
}

您也可以使用Postman进行测试

获取个人帖子

为了抓取单个帖子,我们需要创建一个新的路线和GET方法。

// ...

router.get("/posts/:id", async (req, res) => {
  const post = await Post.findOne({ _id: req.params.id })
  res.send(post)
})

这里,我们注册了一个以 为端点的新路由/posts/:id。这被称为 URL 参数,它允许我们id在路由处理程序中获取帖子的 。因为我们存储在数据库中的每个文档都有一个名为 的唯一标识符ObjectID。我们可以使用此方法找到它,并从对象findOne中传递 id 。req.params

太棒了,现在尝试使用我们的 HTTP 客户端获取单个博客文章。

$ curl http://localhost:5000/api/posts/<OBJECT_ID>
{
  "_id": <OBJECT_ID>,
  "title": "Post 1",
  "content": "Lorem ipsum"
}

看起来它正在工作,但是还有一件事。

如果我们在这个路由中传递了错误的 ObjectID,我们的服务器就会崩溃。服务器崩溃的原因是,当我们获取一个 ObjectID 不存在的帖子时,Promise 会被拒绝,我们的应用程序就会停止工作。

为了防止这种情况,我们可以用 try/catch 块包装我们的代码,这样每当客户端请求不存在的数据时,我们就可以发送自定义错误。

// ...

router.get("/posts/:id", async (req, res) => {
  try {
    const post = await Post.findOne({ _id: req.params.id })
    res.send(post)
  } catch {
    res.status(404)
    res.send({ error: "Post doesn't exist!" })
  }
})

现在,如果我们尝试获取不存在的帖子,我们的服务器仍然会正常运行。

$ curl http://localhost:5000/api/posts/<OBJECT_ID>
{
  "error": "Post doesn't exist!"
}

更新帖子

通常,对单个记录执行更新操作的首选 HTTP 方法是PATCH。那么,让我们创建一个吧!

// ...

router.patch("/posts/:id", async (req, res) => {
  try {
    const post = await Post.findOne({ _id: req.params.id })

    if (req.body.title) {
      post.title = req.body.title
    }

    if (req.body.content) {
      post.content = req.body.content
    }

    await post.save()
    res.send(post)
  } catch {
    res.status(404)
    res.send({ error: "Post doesn't exist!" })
  }
})

我们的更新帖子路由与获取单个帖子路由类似。我们根据 ID 查找帖子,如果帖子不存在则抛出自定义错误。但这次,我们还会更新帖子对象的每个字段,并使用客户端在 .js 内部提供的数据进行填充req.body

我们还想用save方法保存我们的帖子对象,并将更新的帖子数据发送给客户端。

现在,我们可以运行一个PATCH方法到我们的/api/posts/<OBJECT_ID>端点。

$ curl http://localhost:5000/api/posts/<OBJECT_ID> \
    -X PATCH \
    -H "Content-Type: application/json" \
    -d '{"title":"Updated Post", "content":"Updated post content"}'
{
    "__v": 0,
    "_id": <OBJECT_ID>,
    "title": "Updated Post"
    "content": "Updated Post content",
}

删除帖子

最后,我们的最后一步是通过添加删除功能来完成 CRUD 功能。

// ...

router.delete("/posts/:id", async (req, res) => {
  try {
    await Post.deleteOne({ _id: req.params.id })
    res.status(204).send()
  } catch {
    res.status(404)
    res.send({ error: "Post doesn't exist!" })
  }
})

在删除帖子路由中,我们基本上只是直接使用方法向数据库执行删除操作deleteOne,并传递文档 ID。我们不会向用户返回任何内容。

$ curl http://localhost:5000/posts/<OBJECT_ID> -X DELETE -I
HTTP/1.0 204 NO CONTENT
...
鏂囩珷鏉ユ簮锛�https://dev.to/rahmanfadhil/build-rest-api-with-express-mongoose-47f4
PREV
如何在 JavaScript 中对数组进行 CRUD
NEXT
使用 React.js 创建天气应用 - 第一部分