发布于 2026-01-05 10 阅读
0

使用 Express 和 MongoDB 构建 RESTful API DEV's Worldwide Show and Tell Challenge Presented by Mux: Pitch Your Projects!

使用 Express 和 MongoDB 构建 RESTful API

由 Mux 主办的 DEV 全球展示挑战赛:展示你的项目!

今天我们将继续探索 Express。在上一篇教程中,我们使用 Express.js 创建了一个简单的网站。现在,我们将使用 Express 和 MongoDB 构建一个 API。我们将处理的数据是书籍。在本教程结束时,我们将拥有一个 REST API,它允许我们从 MongoDB 数据库中读取、写入、编辑和删除内容。在此过程中,我们将探索与 API 相关的所有主要动词。

“表述性状态转移”(REST)一词由 Roy Fielding 于 2000 年提出。它规定了一套在创建 API 时需要遵循的规则。有了这些规则,最终使用该 API 的其他开发人员就能清楚地知道会发生什么。

让我们开始吧

首先,我们需要通过 npm 初始化项目并安装我们将要使用的软件包来设置项目。

npm init
npm install --save express mongoose
Enter fullscreen mode Exit fullscreen mode

如果你是 Express 的新手,你应该先学习我上面提到的教程,然后再回到这里。

为了快速启动项目,我们将使用与上一个教程相同的代码。创建一个server.js文件并添加以下内容。

import express from 'express';
const app = express();
const port = process.env.PORT || 5656;
// routes go here
app.listen(port, () => {
    console.log(`http://localhost:${port}`)
})
Enter fullscreen mode Exit fullscreen mode

5656这正是我们在上一个教程中使用的方法。如果没有指定备用端口,它将在指定端口启动服务器。

API 的工作原理是控制向发出请求的客户端提供哪些资源。

就我们而言,我们正在创建一个图书 API,这意味着我们将允许其他开发者访问我们数据库中的图书信息。

展示册

除此之外,我们还将在收到针对我们 API 的 GET 请求时提供数据。我们将以 JSON 格式返回数据。

app.get('/api/books', (req, res) => {
    res.json([
            {
                id: 1,
                title: "Alice's Adventures in Wonderland",
                author: "Charles Lutwidge Dodgson"
            },
            {
                id: 2,
                title: Einstein's Dreams",
                author: "Alan Lightman"
            }
        ])
})
app.get('/api/books/2', (req,res)=>{
    res.json(
            {
                id: 2,
                title: Einstein's Dreams",
                author: "Alan Lightman"
            }
        )
})
Enter fullscreen mode Exit fullscreen mode

我们稍后会从数据库中获取该信息,但这里只是为了演示。/api/books返回所有书籍,并/api/books/2返回 ID 为 2 的书籍。

代码重构

在继续之前,让我们重构一下代码,使其在未来更易于管理。

如果我们继续以这种方式添加所有 API 动词,server.js文件会变得非常混乱,代码也难以阅读。Express 允许我们将路由编写在单独的文件中,并将其包含到项目中。

让我们创建一个文件,Routes/bookRouter.js并将这两条路由从这里移过来,server.js如下所示:

import express from 'express';
const bookRouter = express.Router();
bookRouter
    .get('/', (req,res) => {
        res.json(...)
    })
    .get('/2', (req,res) => {
        res.json(...)
    })
export default bookRouter;
Enter fullscreen mode Exit fullscreen mode

注意,我们是如何server.js将两条 GET 路由附加到app`<path>` 的,这两条路由都引用了 `<path>` express()。Express 正好提供了这种机制Router(),使我们能够组织路由。因此,在这种情况下,我们所有的预订路由都将附加到 `<path>` bookRouter

最后,我们需要将该文件导入到server.js我们的 express 应用程序中并使用这些路由。

import bookRouter from './Routes/bookRouter';
...
app.use('/api/Books', bookRouter);
Enter fullscreen mode Exit fullscreen mode

从现在开始,所有路线都将陆续添加Routes/bookRouter.js

使用 MongoDB

上面我们探讨了 API 的基本结构,但实际上,书籍必须存储在数据库中,此 API 才能发挥作用。

如果您之前从未使用过 MongoDB,那么您的电脑上很可能没有安装它。我强烈建议您安装,但这超出了本教程的范围。因此,我们将使用另一个服务提供商:mlab.com。注册 mlab 后,我们可以获得一个免费的 MongoDB 数据库,并且拥有足够的免费空间来满足我们的需求。

它会要求你选择一个服务提供商(三个选项任选其一),然后选择“沙盒”计划(免费),然后点击继续。接下来,它会要求你选择一个地区。我听说有些地区没有免费计划!所以只需保留预先选择的地区即可。

最后,将名称添加到您的数据库中。然后提交订单。注意:如果您收到错误提示,要求您选择其他名称,只需编辑数据库中的名称即可。

点击数据库名称,然后选择“用户”。在这里您需要添加一个用户。该用户将拥有访问数据库的权限。

连接到数据库

我们将使用 mongoose 包来连接和操作数据库。我们已经在开始时安装了 mongoose。

以下是使用 mongoose 访问数据库所需采取的步骤:

  1. 连接到数据库
  2. 构建模型
  3. 执行操作

本着模块化的精神,我们将连接到数据库。server.js

import mongoose from 'mongoose';
const db = mongoose.connect('mongodb://<dbuser>:<dbpassword>@ds125068.mlab.com:25068/api-test2');
Enter fullscreen mode Exit fullscreen mode

然后,在单独的文件中构建模型。models/bookModel.js

import mongoose from 'mongoose';
const Schema = mongoose.Schema;
const bookModel = new Schema({
    title: { type: String   },
    author: { type: String }
})
export default mongoose.model('books', bookModel)
Enter fullscreen mode Exit fullscreen mode

MongoDB 使用集合(MySQL 使用表)。Mongoose 模型需要两个参数:集合名称和模式。在模式中,我们可以指定数据库集合中要包含的字段。正如我们将看到的,上述设置确保无论传递给 Mongoose 的数据是什么,它都只接受集合名称titleauthor模式,而忽略其他属性。

获取:从数据库获取书籍

最后,从现在开始,所有发送到 API 的请求都将与数据库通信。让我们修改之前创建的两个 GET 路由以Routes/bookRouter.js反映这一点。

import express from 'express';
import Book from '../models/bookModel';
const bookRouter = express.Router();
bookRouter.route('/')
    .get((req, res) => {
        Book.find({}, (err, books) => {
            res.json(books)
        })  
    })
bookRouter.route('/:bookId')
    .get((req, res) => {
        Book.findById(req.params.bookId, (err, book) => {
            res.json(book)
        })  
    })
Enter fullscreen mode Exit fullscreen mode

首先我们导入模式。然后,该模式会提供与集合关联的所有 MongoDB 方法(您可以在MongoDB 文档中找到这些方法)。

由于我们将把所有请求方法串联起来,因此我们使用该方法定义路由端点route

在第一个路由配置中,我们获取所有书籍。find()该配置接受两个参数:查询对象和回调函数。查询对象是一个用于筛选数据的对象。请注意,我们传递了一个空对象,因此,我们获取了所有书籍。

第二个 GET 路由更有意思。/:bookId我猜可以把它看作一个占位符。如果我们导航到/api/books/SomeOtherPage,那么SomeOtherPage就可以通过引用/:bookId——这是 Express 的一个特性。req.params.bookId那么就等于SomeOtherPage。实际上,我们期望书籍 ID 与数据库中自动生成的 ID 匹配。

POST:向数据库添加内容

POST 方法用于向数据库添加新内容。在本例中,我们将使用它来添加新书。Routes/bookRouter.js我们仍然可以链式post()调用 POST 方法。get()

...
bookRouter.route('/')
    .get((req, res) => { ...})
    .post((req, res) => {
        let book = new Book({title: 'The Bull', author: 'Saki'});
        book.save();
        res.status(201).send(book) 
    })
Enter fullscreen mode Exit fullscreen mode

mongoose它让数据库操作变得非常简单。我们创建一个新书,将其保存到数据库,然后交付给客户。

正如您所看到的,这种方法不太实用。理想情况下,应该从请求中获取` titleand` 参数。这些信息应该由 API 用户提供。author

为了能够读取请求中包含的数据,我们需要使用一个 Express 中间件来解析传入的数据。这个解析中间件叫做 `parse_middleware` body-parser,它是一个需要通过 NPM 安装的包。

npm install --save body-parser
Enter fullscreen mode Exit fullscreen mode

现在我们可以在 Express 代码中使用这个中间件了。让我们在代码中使用它./server.js

import bodyParser from 'body-parser';
...
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
Enter fullscreen mode Exit fullscreen mode

第一行,我们导入包。第二行和第三行,bodyParser检查传入的数据,并根据数据格式(JSON 或表单数据)进行解析。

回到我们的 POST 路由,body-parser已将传入的数据附加到请求对象。

...
bookRouter.route('/')
    .get((req, res) => { ...})
    .post((req,res) => {
        let book = new Book(req.body); // edited line
        book.save()
        res.status(201).send(book)
    })
Enter fullscreen mode Exit fullscreen mode

就是这样,只要收到 atitle和 an author,就会将其添加到数据库中,并将同一本书传递回去。

请记住,Mongoose schema 会根据提供的请求体内容来判断需要哪些信息。如果请求体req.body包含的数据超过 Mongoose schema 所需,多余的数据将被忽略。例如,如果请求author体中不存在某个req.body标题,则会在数据库中添加一个仅包含该标题的对象,而该标题author本身则根本不存在。Mongoose 在这方面非常灵活。

PUT:编辑数据库中的书籍

我们使用 PUT 请求来编辑特定条目。在我们的图书数据库中,PUT 请求用于编辑一本书。因此,我们切换put()到该/:bookId路由。

bookRouter.route('/:bookId')
    .get(...)
    .put((req,res) => {
        Book.findById(req.params.bookId, (err, book) => {
            book.title = req.body.title;
            book.author = req.body.author;
            book.save()
            res.json(book)
        }) 
    })
Enter fullscreen mode Exit fullscreen mode

正如我们将看到的,我们在执行不同操作时与数据库交互的方式非常相似。在上面的例子中,我们找到一本书,并将数据库中存储的该书对象的属性更改为请求中传递的属性。

补丁:编辑书籍属性

PATCH 功能允许用户编辑书籍对象的特定属性。此功能仍与现有功能关联bookRouter.route('/:bookId')。我们从数据库中提取特定书籍,并修改所有与传入信息匹配的属性。

bookRouter.route('/:bookId')
    .get(...)
    .put(...)
    .patch((req,res)=>{
        Book.findById(req.params.bookId, (err, book) => {
            if(req.body._id){
                delete req.body._id;
            }
            for( let b in req.body ){
                book[b] = req.body[b];
            }
            book.save();
            res.json(book);
        })
    })
Enter fullscreen mode Exit fullscreen mode

我们的 API 用户向 发出 PATCH 请求/api/books/5a76f7373ec6426aaeb91146,并传递他们想要更改的信息——例如,他们可能想要更改作者姓名。

如果他们将 ID 作为要编辑的属性之一传递,_id我们将忽略该请求,因为 ID 是用于组织数据等的唯一标识符,不应该更改。

然后for循环遍历传入对象的剩余属性,并将数据库中找到的属性更新为通过请求传入的属性。

删除:移除书籍

最后,我们希望用户能够删除整本书。

bookRouter.route('/:bookId')
    .get(...)
    .put(...)
    .patch(...)
    .delete((req,res)=>{
        Book.findById(req.params.bookId, (err, book) => {
            book.remove(err => {
                if(err){
                    res.status(500).send(err)
                }
                else{
                    res.status(204).send('removed')
                }
            })
        })
    })//delete
Enter fullscreen mode Exit fullscreen mode

和往常一样,我们通过 ID 找到特定的书籍,然后 mongoose 使我们能够通过remove()对找到的书籍运行该方法来将其删除。

重构:使用自定义中间件

get在编写 `get` 、put`get`patchdelete`get` 方法的代码时,/api/books/:bookId您肯定已经注意到我们重复使用了相同的数据库交互代码。我认为重复的代码能够更清晰地表明,每个方法的代码都是以相同的方式从数据库中检索数据的。

我们的代码中已经使用了中间件——body-parser中间件的工作原理是在我们的代码之前运行!

bookRouter.use('/:bookId', (req, res, next)=>{
    console.log("I run first")
    next()
})
bookRouter.route('/:bookId')
    .get((req,res)=>{
        Book.findById(req.params.bookId, (err, books) => {
            res.json(books)
        })
    })
Enter fullscreen mode Exit fullscreen mode

get收到请求时,/api/books/:bookId消息会被记录(在终端中),然后我们的响应get请求的代码就会运行。

注意,如果我们不包含该next()方法,get请求代码将永远不会执行!

让我们利用这个中间件来做一些有用的东西。

bookRouter.use('/:bookId', (req, res, next)=>{
    Book.findById( req.params.bookId, (err,book)=>{
        if(err)
            res.status(500).send(err)
        else {
            req.book = book;
            next()
        }
    })

})
Enter fullscreen mode Exit fullscreen mode

我们使用中间件从数据库中检索所需的书籍,如果检索成功,则将该book对象附加到请求对象上。

有了这些条件,我们来修改所有与路线相关的动词/:bookId

bookRouter.route('/:bookId')
    .get((req, res) => {
        res.json(req.book)
    }) // end get Books/:bookId 
    .put((req,res) => {
        req.book.title = req.body.title;
        req.book.author = req.body.author;
        req.book.save()
        res.json(req.book)
    })
    .patch((req,res)=>{
        if(req.body._id){
            delete req.body._id;
        }
        for( let p in req.body ){
            req.book[p] = req.body[p]
        }
        req.book.save()
        res.json(req.book)
    })//patch
    .delete((req,res)=>{
        req.book.remove(err => {
            if(err){
                res.status(500).send(err)
            }
            else{
                res.status(204).send('removed')
            }
        })
    })//delete
Enter fullscreen mode Exit fullscreen mode

区别在于,数据库交互在中间件中只编写一次,但仍然在每个请求中运行。

好了,REST API 已经完成。

邮差

要测试所有路由是否都能正常工作,您可以使用 Postman,这是一个Chrome 应用程序。

以下是如何发出帖子请求的方法。

在顶部选择方法/动词——上面我们选择了 POST。然后输入 API 端点。接下来,只需选择Headers并添加application/json作为参数,Content-Type并在请求体中(选中raw选项)传递要传递给 API 的 JSON 内容。在上面的截图中,我们传递了必需的title参数author

感谢阅读

代码可以从GitHub下载。

文章来源:https://dev.to/aurelkurtula/building-a-restful-api-with-express-and-mongodb--3mmh