References across collections
用户和便签应该是一对多的关系,如果是关系型数据库,便签可以将user作为外键
mongoDB是文档型数据库
可以在note里存入user的id
[
{
content: 'HTML is easy',
important: false,
_id: 221212,
user: 123456,
},
-- snip --
]
或者在user里存入note的id组成的数组
[
{
username: 'mluukkai',
_id: 123456,
notes: [221212, 221255],
},
-- snip --
]
又或者可以直接将具体的notes作为user的属性
[
{
username: 'mluukkai',
_id: 123456,
notes: [
{
content: 'HTML is easy',
important: false,
},
{
content: 'The most important operations of HTTP protocol are GET and POST',
important: true,
},
],
},
-- snip --
]
Mongoose schema for users
这里,我们选择将 note 的 id 以数组的形式存储到 user 当中
让我们定义一个 model 来表示 User 吧, models/user.js 代码如下:
const mongoose = require('mongoose')
const userSchema = new mongoose.Schema({
username: String,
name: String,
passwordHash: String,
notes: [
{
type: mongoose.Schema.Types.ObjectId,
ref: 'Note'
}
],
})
userSchema.set('toJSON', {
transform: (document, returnedObject) => {
returnedObject.id = returnedObject._id.toString()
delete returnedObject._id
delete returnedObject.__v
// the passwordHash should not be revealed
delete returnedObject.passwordHash
}
})
const User = mongoose.model('User', userSchema)
module.exports = User
让我们展开 model/note.js 文件中 note 的 schema,让 note 包含其创建者的信息。
const noteSchema = new mongoose.Schema({
content: {
type: String,
required: true,
minlength: 5
},
date: Date,
important: Boolean,
user: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User'
}
})
Creating users
让我们来实现一个创建 User 的路由
密码不能明文存储,安装bcrypt 用来生成密码的哈希值
注:windows安装bcrypt会报错,改用bcryptjs
npm install bcryptjs
controllers/users.js定义user路由
const bcrypt = require('bcryptjs')
const usersRouter = require('express').Router()
const User = require('../models/user')
usersRouter.post('/', async (request, response) => {
const body = request.body
const saltRounds = 10
const passwordHash = await bcrypt.hash(body.password, saltRounds)
const user = new User({
username: body.username,
name: body.name,
passwordHash,
})
const savedUser = await user.save()
response.json(savedUser)
})
module.exports = usersRouter
在app.js中使用路由
const usersRouter = require('./controllers/users')
// ...
app.use('/api/users', usersRouter)
编写自动化测试所需的工作量要少得多,而且它将使应用的开发更加容易。
Creating a new note
创建note的代码也要跟着改变
const User = require('../models/user')
//...
notesRouter.post('/', async (request, response, next) => {
const body = request.body
const user = await User.findById(body.userId)
const note = new Note({
content: body.content,
important: body.important === undefined ? false : body.important,
date: new Date(),
user: user._id
})
const savedNote = await note.save()
user.notes = user.notes.concat(savedNote._id)
await user.save()
response.json(savedNote.)
})
Populate
我们希望我们的 API 以这样的方式工作,即当一个 HTTP GET 请求到/api/users 路由时,User 对象同样包含其创建 Note 的内容,而不仅仅是 Note 的 id。 在关系型数据库中,这个功能需求可以通过 join query 实现。
Mongoose 的 join 是通过populate 方法完成的。让我们更新返回所有 User 的路由:
usersRouter.get('/', async (request, response) => {
const users = await User
.find({}).populate('notes')
response.json(users)
})
这样获取到的user就包含notes内容了
我们可以使用 populate 的参数来选择要显示的内容,1表示显示