使用 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
如果你是 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}`)
})
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"
}
)
})
我们稍后会从数据库中获取该信息,但这里只是为了演示。/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;
注意,我们是如何server.js将两条 GET 路由附加到app`<path>` 的,这两条路由都引用了 `<path>` express()。Express 正好提供了这种机制Router(),使我们能够组织路由。因此,在这种情况下,我们所有的预订路由都将附加到 `<path>` bookRouter。
最后,我们需要将该文件导入到server.js我们的 express 应用程序中并使用这些路由。
import bookRouter from './Routes/bookRouter';
...
app.use('/api/Books', bookRouter);
从现在开始,所有路线都将陆续添加Routes/bookRouter.js。
使用 MongoDB
上面我们探讨了 API 的基本结构,但实际上,书籍必须存储在数据库中,此 API 才能发挥作用。
如果您之前从未使用过 MongoDB,那么您的电脑上很可能没有安装它。我强烈建议您安装,但这超出了本教程的范围。因此,我们将使用另一个服务提供商:mlab.com。注册 mlab 后,我们可以获得一个免费的 MongoDB 数据库,并且拥有足够的免费空间来满足我们的需求。
它会要求你选择一个服务提供商(三个选项任选其一),然后选择“沙盒”计划(免费),然后点击继续。接下来,它会要求你选择一个地区。我听说有些地区没有免费计划!所以只需保留预先选择的地区即可。
最后,将名称添加到您的数据库中。然后提交订单。注意:如果您收到错误提示,要求您选择其他名称,只需编辑数据库中的名称即可。
点击数据库名称,然后选择“用户”。在这里您需要添加一个用户。该用户将拥有访问数据库的权限。
连接到数据库
我们将使用 mongoose 包来连接和操作数据库。我们已经在开始时安装了 mongoose。
以下是使用 mongoose 访问数据库所需采取的步骤:
- 连接到数据库
- 构建模型
- 执行操作
本着模块化的精神,我们将连接到数据库。server.js
import mongoose from 'mongoose';
const db = mongoose.connect('mongodb://<dbuser>:<dbpassword>@ds125068.mlab.com:25068/api-test2');
然后,在单独的文件中构建模型。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)
MongoDB 使用集合(MySQL 使用表)。Mongoose 模型需要两个参数:集合名称和模式。在模式中,我们可以指定数据库集合中要包含的字段。正如我们将看到的,上述设置确保无论传递给 Mongoose 的数据是什么,它都只接受集合名称title和author模式,而忽略其他属性。
获取:从数据库获取书籍
最后,从现在开始,所有发送到 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)
})
})
首先我们导入模式。然后,该模式会提供与集合关联的所有 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)
})
mongoose它让数据库操作变得非常简单。我们创建一个新书,将其保存到数据库,然后交付给客户。
正如您所看到的,这种方法不太实用。理想情况下,应该从请求中获取` titleand` 参数。这些信息应该由 API 用户提供。author
为了能够读取请求中包含的数据,我们需要使用一个 Express 中间件来解析传入的数据。这个解析中间件叫做 `parse_middleware` body-parser,它是一个需要通过 NPM 安装的包。
npm install --save body-parser
现在我们可以在 Express 代码中使用这个中间件了。让我们在代码中使用它./server.js。
import bodyParser from 'body-parser';
...
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
第一行,我们导入包。第二行和第三行,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)
})
就是这样,只要收到 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)
})
})
正如我们将看到的,我们在执行不同操作时与数据库交互的方式非常相似。在上面的例子中,我们找到一本书,并将数据库中存储的该书对象的属性更改为请求中传递的属性。
补丁:编辑书籍属性
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);
})
})
我们的 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
和往常一样,我们通过 ID 找到特定的书籍,然后 mongoose 使我们能够通过remove()对找到的书籍运行该方法来将其删除。
重构:使用自定义中间件
get在编写 `get` 、put`get`patch和delete`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)
})
})
当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()
}
})
})
我们使用中间件从数据库中检索所需的书籍,如果检索成功,则将该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
区别在于,数据库交互在中间件中只编写一次,但仍然在每个请求中运行。
好了,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


