일반적으로 noSQL 기반의 몽고를 사용하게 되면 nodejs용 ODM (Object Data Mapping) 프레임워크를 사용하게 된다.
스키마 정의
const mongoose = require('mongoose');가장 일반적인 mongoose 예제이다.
const Schema = mongoose.Schema;
const PostSchema = new Schema({
title:String
});
module.exports = PostSchema;
mongoose를 import 한 후 schema 메서드를 이용하여 정의하여준다.
그리고 title을 String으로 정의한다.
const mongoose = require('mongoose');다른 스키마를 참조하였다.
const Schema = mongoose.Schema;
const CommentSchema = new Schema({
content: String,
user: { type: Schema.Types.ObjectId, ref: 'user' }
});
const Comment = mongoose.model('comment', CommentSchema);
module.exports = Comment;
content의 경우 String 타입이지만 User의 경우 user 스키마에서 데이터를 가져도록 정의한다.
Join의 개념과 비슷하며 실제 사용 예시는 아래쪽에 자세히 다루도록 하겠다.
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const PostSchema = require('./post');
const UserSchema = new Schema({
name: {
type: String,
validate: {
validator: (name) => name.length > 2,
message: 'Name must be longer than 2 characters.'
},
required: [true, 'Name is required.']
},
posts: [PostSchema],
likes: Number,
blogPosts: [{
type: Schema.Types.ObjectId,
ref: 'blogPost'
}]
});
UserSchema.virtual('postCount').get(function() {
return this.posts.length;
});
const User = mongoose.model('user', UserSchema);
module.exports = User;
위의 예제를 보자
위에서는 이름을 String 형식으로 그리고 validator를 통하 검증을 넣어주었다.
두번째로 [PostSchema]의 경우 다른 schema 내용을 가져와 이용할 수 있다.
Virtual의 경우 실제로 존재하지는 않는 custom 내용이라 할 수 있다.
get의 경우 어떻게 데이터를 return 하여줄지에 대한 내용이고 setter의 경우 해당 데이터를 넣음으로써
내용을 재정의 하여줄 수 있다.
virtual에서 this는 나 자신의 스키마다
ex)
personSchema.virtual('name.full').get(function () {
return this.name.full;
}).set(function(name) {
var split = name.split(' ');
this.name.first = split[0];
this.name.last = split[1];
});
CRUD
Create
const joe = new User({name: 'Joe'});인스턴스를 만든 후 인스턴스의 save를 통해 저장한다. 이때 isNew를 통하여 데이터가 정상적으로 들어갔는지 검증
joe.save()
.then(() => {
// Has joe been saved successfully?
assert(!joe.isNew);
done();
});
Update
인스턴스 메서드
joe.set('name', 'Alex');set을 통하여 쉽게 바꿀 수 있다.
joe.update({ name: 'Alex' })
클래스 메서드
User.update({ name: 'Joe' }, { name: 'Alex' }),Joe를 찾아 Alex로 업데이트
User.findByIdAndUpdate(joe._id, { name: 'Alex' }),
User.findOneAndUpdate({ name: 'Joe' }, { name: 'Alex' }),
findOne의 경우 검색되는 첫번째 쿼리만 반영
Delete
joe.remove()
인스턴스 메서드 제거
User.remove({ name: 'Joe' })Joe인것을 제거
User.findOneAndRemove({ name: 'Joe' })
joe인것 하나만 제거
Nesting 스키마
const joe = new User({
name: 'Joe',
posts: [{title: 'PostTitle'}]
//자동으로 post schema에 데이터를 처리함
});
이렇게 사용할경우 자동으로 Post 스키마에서 title이 postTitle인 것을 생성한 후 Id값을 넣어준다(ref로 참조를 해주어야함)
user.posts.push({title: 'New Post'});
다중선언도 가능
const post = user.posts[0];post를 검색 한 후 삭제 마지막으로 저장하면
post.remove();
return user.save();
assert(user.posts.length === 0);
가 True가 된다.
Virtual Type
const joe = new User({
name: 'Joe',
posts: [{title: 'PostTitle'}]
});
// joe.postCount //now undefined
joe.save()
.then(() => User.findOne({name: 'Joe'}))
.then((user) => {
assert(joe.postCount === 1);
done();
})
virtual type의 경우 따로 세팅해 주지 않아도 바로 사용할 수 있다.
Validation Error Test
describe('Validatiing records', ()=>{
it('require a user name', ()=>{
const user = new User({name: undefined});
const validationResult = user.validateSync();
// const message = validationResult.errors.name.message;
const { message } = validationResult.errors.name;
console.log(message);
assert( message == 'Name is required.')
});
it('disallows invalid records from being saved', (done) => {
const user = new User({ name: 'Al' });
user.save()
.catch((validationResult) => {
const { message } = validationResult.errors.name;
assert(message === 'Name must be longer than 2 characters.');
done();
});
});
});
위와 같이 validateSync를 통해 에러 여부를 확인할 수 있다.
Populate
describe('Assocations', () => {
let joe, blogPost, comment;
beforeEach((done) => {
joe = new User({name: 'Joe'});
blogPost = new BlogPost({title: 'JS is Great', content: 'Yep it really is'});
comment = new Comment({content: 'Congrats on great post'});
joe.blogPosts.push(blogPost);
blogPost.comments.push(comment);
comment.user = joe;
Promise.all([joe.save(), blogPost.save(), comment.save()])
.then(() => done());
});
//only 붙이면 이것만 테스트합니다
it('save a relation between a user and a blogpost', (done) => {
User.findOne({name: 'Joe'})
.populate('blogPosts')
.then((user) => {
assert(user.blogPosts[0].title = 'JS is Great');
done();
});
});
it('saves a full relatin graph', (done) => {
User.findOne({name: 'Joe'})
.populate({
//populate 안에 populate
path: 'blogPosts', //가져올거 정해주고 //그 내용안에서 populate 할 걸 정해준다.
populate: {
path: 'comments', //조회할 칼럼(?)
model: 'comment', //기준으로 가져올 데이터
populate:{
path: 'user',
model: 'user'
}
}
}).then((user) => {
assert(user.name === 'Joe');
assert(user.blogPosts[0].title === 'JS is Great');
done();
})
})
});
populate 메서드에서 path를 선언하여 조회할 칼럼을 Join처럼 가져올 수 있다.