References across collections

用户和便签应该是一对多的关系,如果是关系型数据库,便签可以将user作为外键
mongoDB是文档型数据库
可以在note里存入user的id

  1. [
  2. {
  3. content: 'HTML is easy',
  4. important: false,
  5. _id: 221212,
  6. user: 123456,
  7. },
  8. -- snip --
  9. ]

或者在user里存入note的id组成的数组

  1. [
  2. {
  3. username: 'mluukkai',
  4. _id: 123456,
  5. notes: [221212, 221255],
  6. },
  7. -- snip --
  8. ]

又或者可以直接将具体的notes作为user的属性

  1. [
  2. {
  3. username: 'mluukkai',
  4. _id: 123456,
  5. notes: [
  6. {
  7. content: 'HTML is easy',
  8. important: false,
  9. },
  10. {
  11. content: 'The most important operations of HTTP protocol are GET and POST',
  12. important: true,
  13. },
  14. ],
  15. },
  16. -- snip --
  17. ]

Mongoose schema for users

这里,我们选择将 note 的 id 以数组的形式存储到 user 当中
让我们定义一个 model 来表示 User 吧, models/user.js 代码如下:

  1. const mongoose = require('mongoose')
  2. const userSchema = new mongoose.Schema({
  3. username: String,
  4. name: String,
  5. passwordHash: String,
  6. notes: [
  7. {
  8. type: mongoose.Schema.Types.ObjectId,
  9. ref: 'Note'
  10. }
  11. ],
  12. })
  13. userSchema.set('toJSON', {
  14. transform: (document, returnedObject) => {
  15. returnedObject.id = returnedObject._id.toString()
  16. delete returnedObject._id
  17. delete returnedObject.__v
  18. // the passwordHash should not be revealed
  19. delete returnedObject.passwordHash
  20. }
  21. })
  22. const User = mongoose.model('User', userSchema)
  23. module.exports = User

让我们展开 model/note.js 文件中 note 的 schema,让 note 包含其创建者的信息。

  1. const noteSchema = new mongoose.Schema({
  2. content: {
  3. type: String,
  4. required: true,
  5. minlength: 5
  6. },
  7. date: Date,
  8. important: Boolean,
  9. user: {
  10. type: mongoose.Schema.Types.ObjectId,
  11. ref: 'User'
  12. }
  13. })

Creating users

让我们来实现一个创建 User 的路由
密码不能明文存储,安装bcrypt 用来生成密码的哈希值
注:windows安装bcrypt会报错,改用bcryptjs

  1. npm install bcryptjs

controllers/users.js定义user路由

  1. const bcrypt = require('bcryptjs')
  2. const usersRouter = require('express').Router()
  3. const User = require('../models/user')
  4. usersRouter.post('/', async (request, response) => {
  5. const body = request.body
  6. const saltRounds = 10
  7. const passwordHash = await bcrypt.hash(body.password, saltRounds)
  8. const user = new User({
  9. username: body.username,
  10. name: body.name,
  11. passwordHash,
  12. })
  13. const savedUser = await user.save()
  14. response.json(savedUser)
  15. })
  16. module.exports = usersRouter

在app.js中使用路由

  1. const usersRouter = require('./controllers/users')
  2. // ...
  3. app.use('/api/users', usersRouter)

编写自动化测试所需的工作量要少得多,而且它将使应用的开发更加容易。

Creating a new note

创建note的代码也要跟着改变

  1. const User = require('../models/user')
  2. //...
  3. notesRouter.post('/', async (request, response, next) => {
  4. const body = request.body
  5. const user = await User.findById(body.userId)
  6. const note = new Note({
  7. content: body.content,
  8. important: body.important === undefined ? false : body.important,
  9. date: new Date(),
  10. user: user._id
  11. })
  12. const savedNote = await note.save()
  13. user.notes = user.notes.concat(savedNote._id)
  14. await user.save()
  15. response.json(savedNote.)
  16. })

Populate

我们希望我们的 API 以这样的方式工作,即当一个 HTTP GET 请求到/api/users 路由时,User 对象同样包含其创建 Note 的内容,而不仅仅是 Note 的 id。 在关系型数据库中,这个功能需求可以通过 join query 实现。
Mongoose 的 join 是通过populate 方法完成的。让我们更新返回所有 User 的路由:

  1. usersRouter.get('/', async (request, response) => {
  2. const users = await User
  3. .find({}).populate('notes')
  4. response.json(users)
  5. })

这样获取到的user就包含notes内容了
image.png
我们可以使用 populate 的参数来选择要显示的内容,1表示显示
image.png