案例1:在 Nodejs 中操作 MongoDB
MongoDB 官方提供了很多驱动(Drivers)用于操作 MongoDB。
这些驱动程序几乎包括所有主流后端语言,实际上就是对应到各个平台上的程序包,用来连接操作 MongoDB。
下面演示一个简单的 Node.js 操作 MongoDB 的案例,参考:MongoDB Node Driver — Node.js
请保证 MongoDB 服务是开启状态。
初始化示例项目
mkdir node-mongodb-demo
cd node-mongdb-demo
npm init -y
# mongodb 是官方提供的 Node.js 下操作 MongoDB 的工具包
npm i mongodb
连接 MongoDB
const { MongoClient } = require('mongodb')
// 创建连接数据库的客户端实例
const client = new MongoClient('mongodb://127.0.0.1:27017')
async function run() {
try {
// 将客户端连接到数据库服务
await client.connect()
// 连接 test 数据库
const testDb = client.db('test')
// 获取 inventory 集合
const inventoryCollection = testDb.collection('inventory')
// 查询文档
const ret = await inventoryCollection.find()
// find 方法返回管理查询结果的指针,可使用 toArray() 方法遍历查看
console.log(await ret.toArray())
} catch (err) {
// 连接失败
console.log('连接失败')
} finally {
// 确保完成/出错时客户端会关闭连接
await client.close()
}
}
run()
CRUD 操作
各个平台的 CRUD 操作方法都可以在官方文档 MongoDB CRUD Operations 中找到,切换页面中的 “Select your language” 选择使用的语言。
创建文档
const ret = await inventoryCollection.insertOne({ item: 'card', qty: 15 })
console.log(ret)
查询文档
const ret = await inventoryCollection.find({ item: 'card' })
console.log(await ret.toArray())
const ret = await inventoryCollection.findOne({ item: 'card' })
console.log(ret)
修改文档
const ret = await inventoryCollection.updateOne(
{
item: 'card'
},
{
$set: {
qty: 100
}
}
)
console.log(ret)
删除文档
下例使用 _id
匹配要删除的文档,默认情况下 _id
是 ObjectId 对象,需要用 ObjectId
类去创建实例。
注意:MongoDB 之前版本使用了名为
ObjectID
的方法,现在这个名字已被弃用,推荐并改为ObjectId
。
const { ObjectId } = require('mongodb')
await inventoryCollection.deleteOne({
_id: ObjectId('61b691a3db3aa35bcac3f440')
})
案例2:MongoDB 数据库结合 Web 服务
案例介绍
本例搭建一个支持 MongoDB 数据库 CRUD 操作的 Web 接口服务,用来进行博客文章的管理。
接口设计
本例接口基于 RESTful 接口规范,参考:
本例接口数据格式:application/json
本例使用 postman 调试测试接口。
创建文章
- 请求路径:
POST
/articles - 请求参数:Body
- title
- description
- body
- tagList
请求体示例:
{
"article": {
"title": "How to train your dragon",
"description": "Ever wonder how?",
"body": "You have to believe",
"tagList": ["reactjs", "angularjs", "dragons"]
}
}
响应数据示例:
- 状态码:201
- 响应数据:
{
"article": {
"_id": 123,
"title": "How to train your dragon",
"description": "Ever wonder how?",
"body": "It takes a Jacobian",
"tagList": ["dragons", "training"]
}
}
获取文章列表
- 请求路径:
GET
/articles - 请求参数:Query
- _page:页码
- _size:每页大小
响应数据示例:
- 状态码:200
- 响应数据:
{
"articles":[{
"_id": "how-to-train-your-dragon",
"title": "How to train your dragon",
"description": "Ever wonder how?",
"body": "It takes a Jacobian",
"tagList": ["dragons", "training"],
"createdAt": "2016-02-18T03:22:56.637Z",
"updatedAt": "2016-02-18T03:48:35.824Z"
}, {
"_id": "how-to-train-your-dragon-2",
"title": "How to train your dragon 2",
"description": "So toothless",
"body": "It a dragon",
"tagList": ["dragons", "training"],
"createdAt": "2016-02-18T03:22:56.637Z",
"updatedAt": "2016-02-18T03:48:35.824Z"
}],
"articlesCount": 2
}
获取单个文章
- 请求路径:
GET
/articles/:id
响应数据示例:
- 状态码:200
- 响应数据:
{
"article": {
"_id": "dsa7dsa",
"title": "How to train your dragon",
"description": "Ever wonder how?",
"body": "It takes a Jacobian",
"tagList": ["dragons", "training"],
"createdAt": "2016-02-18T03:22:56.637Z",
"updatedAt": "2016-02-18T03:48:35.824Z"
}
}
更新文章
- 请求路径:
PATCH
/articles/:id - 请求参数:Body
- title
- description
- body
- tagList
请求体示例:
{
"article": {
"title": "Did you train your dragon?"
}
}
响应数据示例:
- 状态码:201
- 响应数据:
{
"article": {
"_id": 123,
"title": "How to train your dragon",
"description": "Ever wonder how?",
"body": "It takes a Jacobian",
"tagList": ["dragons", "training"],
"createdAt": "2016-02-18T03:22:56.637Z",
"updatedAt": "2016-02-18T03:48:35.824Z"
}
}
删除文章
- 请求路径:
DELETE
/articles/:id
响应数据示例:
- 状态码:204
初始化项目
mkdir article-end
cd article-end
npm init -y
npm i express mongodb
创建入口文件 app.js:
// app.js
const express = require('express')
const app = express()
// 配置解析请求体数据 application/json
// 该中间件会将解析到的请求体数据放到 req.body
app.use(express.json())
app.get('/', (req, res) => {
res.send('Hello World!')
})
app.listen(3000, () => {
console.log('app listenning at port 3000.')
})
启动服务:
# 建议全局安装 nodemon 用于启动服务
npm i -g nodemon
# 启动服务
nodemon app.js
路由配置
// app.js
const express = require('express')
const app = express()
// 配置解析请求体数据 application/json
// 该中间件会将解析到的请求体数据放到 req.body
app.use(express.json())
app.get('/', (req, res) => {
res.send('Hello World!')
})
// 创建文章
app.post('/articles', (req, res) => {
res.send('post /articles')
})
// 获取文章列表
app.get('/articles', (req, res) => {
res.send('get /articles')
})
// 获取单个文章
app.get('/articles/:id', (req, res) => {
res.send('get /articles/:id')
})
// 更新文章
app.patch('/articles/:id', (req, res) => {
res.send('patch /articles/:id')
})
// 删除文章
app.delete('/articles/:id', (req, res) => {
res.send('delete /articles/:id')
})
app.listen(3000, () => {
console.log('app listenning at port 3000.')
})
创建文章
// app.js
const express = require('express')
// 数据库
const { MongoClient } = require('mongodb')
const connectUri = 'mongodb://localhost:27017'
const dbClient = new MongoClient(connectUri)
//...
// 创建文章
app.post('/articles', async (req, res) => {
try {
// 1. 获取客户端表单数据
const { article } = req.body
// 2. 数据验证
if (!article || !article.title || !article.description || !article.body) {
return res.status(422).json({
error: '请求参数不符合规则要求'
})
}
// 3. 把验证通过的数据插入数据库中
await dbClient.connect()
const collection = dbClient.db('test').collection('articles')
const ret = await collection.insertOne(article)
article._id = ret.insertedId
// 4. 发送成功/失败响应
res.status(201).json(article)
} catch (err) {
res.status(500).json({
error: err.message
})
}
})
//...
统一处理服务端错误
//...
// 创建文章
app.post('/articles', async (req, res, next) => {
try {
//...
} catch (err) {
// 由错误处理中间件统一处理
next(err)
}
})
//...
// 错误处理中间件
// 在此之前所有路由中调用 next(err) 都会进入这里
// 注意:必须具备 4 个参数才会被标识为错误处理中间件
app.use((err, req, res, next) => {
res.status(500).json({
error: err.message
})
})
app.listen(3000, () => {
console.log('app listenning at port 3000.')
})
获取文章列表
// 获取文章列表
app.get('/articles', async (req, res, next) => {
try {
let { _page = 1, _size = 10 } = req.query
_page = parseInt(_page)
_size = parseInt(_size)
await dbClient.connect()
const collection = dbClient.db('test').collection('articles')
const ret = await collection
.find() // 查询数据(获取游标)
.skip((_page - 1) * _size) // 跳过多少条
.limit(_size) // 获取多少条
const articles = await ret.toArray()
const articlesCount = await collection.countDocuments()
res.status(200).json({
articles,
articlesCount
})
} catch (err) {
next(err)
}
})
获取文章详情
// 引入 ObjectId 方法
const { MongoClient, ObjectId } = require('mongodb')
//...
// 获取单个文章
app.get('/articles/:id', async (req, res, next) => {
try {
await dbClient.connect()
const collection = dbClient.db('test').collection('articles')
const article = await collection.findOne({ _id: ObjectId(req.params.id) })
res.status(200).json({
article
})
} catch (err) {
next(err)
}
})
更新文章
// 更新文章
app.patch('/articles/:id', async (req, res, next) => {
try {
await dbClient.connect()
const collection = dbClient.db('test').collection('articles')
const article = await collection.updateOne(
{ _id: ObjectId(req.params.id) },
{
$set: req.body.article
}
)
// req.body.article 在更新后会被变更为更新结果的状态信息
// 查询更新后的数据(仅匹配 _id 可以直接简写为传参)
const ret = await collection.findOne(ObjectId(req.params.id))
res.status(201).json(ret)
} catch (err) {
next(err)
}
})
删除文章
// 删除文章
app.delete('/articles/:id', async (req, res, next) => {
try {
await dbClient.connect()
const collection = dbClient.db('test').collection('articles')
const article = await collection.deleteOne({ _id: ObjectId(req.params.id) })
res.status(204).json({})
} catch (err) {
next(err)
}
})