案例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 国内外研究现状 mongodb 案例_json

本例搭建一个支持 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)
  }
})