Node.js Mongoose 사용해보기 - 1
2018-06-17 23:13:03

mongodb 설치, mongoose로 mongodb 연결 및 스키마 정의, create, read까지 정리한 글

mongodb 설치

mac 기준 homebrew로 편하게 ㄱㄱ

1
2
3
4
5
brew install mongodb
sudo mkdir -p /data/db
whoami # 사용자명 출력
sudo chown 터미널에 나온 사용자명 /data/db
brew services start mongo

사용한 mongoose 버전은 5.6.8이다. 버전 앞이 바뀌엇구먼 허허

코드는 귀찬으니 mongoose 쪽 부분만.

연결 설정

연결은 4.X.X랑 아주 쪼금 바뀌엇다.

1
2
3
4
5
6
7
8
9
10
const mongoose = require('mongoose');
mongoose.Promise = global.Promise;
const mongodb = mongoose.connect('mongodb://localhost:27017/localtest', {
useNewUrlParser: true // 이 부분이 다른걸로 바뀜
});
mongodb.then(() => {
console.log('mongodb has been connected');
}).catch((err) => {
console.log('mongodb connect err', err);
});

스키마 정의

스키마를 정의해두면 직접 db 안을 안 봐도 구조가 어떠케 생겨먹엇는지 알 수가 잇고

연결이 잘 되어 잇으면 실제 컬렉션에 데이터 삽입할 떄 정의한대로 컬렉션 생성을 한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29


'use strict'

const mongoose = require('mongoose');
const Schema = mongoose.Schema;

const userSchema = new Schema({ // 유저 스키마 정의
userName: {
type: String,
required: [true, '아이디는 필수입니다.'] // 필수 값 validation
},
userNum: {
type: Number,
required: [true, '등번호는 필수입니다.'] // 필수 값 validation
},
position: String,
userMail: String,
user: {
type: Schema.Types.ObjectId, // populate를 이용하기 위한 타입 처리
ref: 'teams'
},
createAt: {
type: Date,
default: Date.now() // 기본 값 오늘날짜로 설정
}
});

module.exports = mongoose.model('users', userSchema); // user == 컬렉션명

여전히 컬렉션을 생성 할 때 user 이런식으로 정의하면 복수형인 users로 자동 변환 된다.

스키마에서 쓸 수 잇는 데이터 타입은 정리를 해두자.

  • String = 문자열
  • Number = 숫자
  • Date = 날짜형
  • Boolean = true / false
  • Mixed = 잡종, 어떤 형이든 다 가능.
  • ObjectId = mongodb에서 쓰는 고유 아디 형식 같음.
  • Array = 배열

document 생성

mongodb는 하나의 document가 mysql에서 하나의 row와 같음.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
const userModel = require('../models/users'); // 정의한 스키마
// @TODO: 1 document 생성
router.get('/create', (req, res) => {
let user = new userModel({ // 새 객체에 넣을 데이터 쓴다.
userName: '크리스티아누 날강두',
userNum: 7,
position: '공격수',
team: '5d4c412caf60c707c225565d', // 일단 무시 조인할 때 설명.
userMail: '날강두@유벤투스.com'
});

user.save((err, result) => { // .save()를 호출하면 끝.
if (err) {
res.json(err);
} else {
res.json(result);
}
});
});

실행하면 다음과 같은 결과를 볼 수 잇다.

1
2
3
4
5
6
7
8
9
10
{
"createAt": "2019-08-08T15:37:45.028Z",
"_id": "5d4c41cc2a822207ecb315e8", // mongo 고유 ID
"userName": "크리스티아누 날강두",
"userNum": 7,
"position": "공격수",
"userMail": "날강두@유벤투스.com",
"team": "5d4c412caf60c707c225565d",
"__v": 0
}

여러 개 document 생성

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// @TODO: 2개 이상의 document를 생성.
router.get('/manyCreate', (req, res) => {
let users = [
{
userName: '모하메드 살라',
userNum: 11,
position: '공격수',
team: '5d4c412caf60c707c225565e', // 일단 무시.
userMail: '이집트메시@리버풀.com'
},
{
userName: '피에르에메릭 오바메양',
userNum: 17,
position: '공격수',
team: '5d4c412caf60c707c225565f',
userMail: '득점왕@아스날.com'
},
{
userName: '메수트 외질',
userNum: 11,
position: '미드필더',
team: '5d4c412caf60c707c225565f',
userMail: '노랑머리@아스날.com'
}
];
userModel.insertMany(users, (err, result) => {
if (err) {
res.json(err);
} else {
res.json(result);
}
});
});

배열 안에 object로 삽입할 데이터를 적어두고 insertMany를 호출하면 끝.

아 예전에 중간에 데이터가 빠져서 엿나.. 그 밖 여러 요인으로 인해 정상적인 처리가 안되엇을 때

컬렉션에 있는 데이터들이 아주 개판이 되엇던 적이 잇엇던 걸로 기억 한다 암튼 조심.

mongoose는 삽입하면 바로바로 뭐가 들어간지 리턴이 되서 참 편하다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
[
{
"createAt": "2019-08-08T15:37:45.028Z",
"_id": "5d4c42242a822207ecb315e9",
"userName": "모하메드 살라",
"userNum": 11,
"position": "공격수",
"team": "5d4c412caf60c707c225565e",
"userMail": "이집트메시@리버풀.com",
"__v": 0
},
{
"createAt": "2019-08-08T15:37:45.028Z",
"_id": "5d4c42242a822207ecb315ea",
"userName": "피에르에메릭 오바메양",
"userNum": 17,
"position": "공격수",
"team": "5d4c412caf60c707c225565f",
"userMail": "득점왕@아스날.com",
"__v": 0
},
{
"createAt": "2019-08-08T15:37:45.028Z",
"_id": "5d4c42242a822207ecb315eb",
"userName": "메수트 외질",
"userNum": 11,
"position": "미드필더",
"team": "5d4c412caf60c707c225565f",
"userMail": "노랑머리@아스날.com",
"__v": 0
}
]

아 그리고 아까 스키마에 정의 해놓은 required에 대한 처리는 어떻게 하는지 적어둔다.

새로 생성하는 document에 required를 걸어논 키의 데이터를 빼먹고 실행해보자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// @TODO: mongoose validation
router.get('/validation', (req, res) => {
var user = new userModel({
userName: '해리 맥과이어',
userNum: null,
position: '수비수',
userMail: '비싼몸@맨유.com'
});
let validateErr = user.validateSync(); // err return
user.save((err, result) => {
if (err) {
res.json(err);
} else if (result && !validateErr) {
res.json(result);
} else {
res.json(validateErr);
}
});
});

required에 걸리므로 에러를 할당 받는다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
{
"errors": {
"userNum": {
"message": "등번호는 필수입니다.",
"name": "ValidatorError",
"properties": {
"message": "등번호는 필수입니다.",
"type": "required",
"path": "userNum",
"value": null
},
"kind": "required",
"path": "userNum",
"value": null
}
},
"_message": "users validation failed",
"message": "users validation failed: userNum: 등번호는 필수입니다.",
"name": "ValidationError"
}

문서 읽기

1
2
3
4
5
6
7
8
9
10
// @TODO: 조회 {} 오브젝트 안에 조건이 들어감.
router.get('/find', (req, res) => {
userModel.find({}, (err, data) => {
if (err) {
res.json(err);
} else {
res.json(data);
}
});
});

find를 호출하면 된다 {} 안이 where 절이다. 다양한 조건을 걸 수가 이씀.

현재는 조건이 업으니 모두 출력 한다. 그리고 find류는 모두 Array타입으로 리턴 된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
[
{
"createAt": "2019-08-08T15:37:45.028Z",
"_id": "5d4c41cc2a822207ecb315e8",
"userName": "크리스티아누 날강두",
"userNum": 7,
"position": "공격수",
"userMail": "날강두@유벤투스.com",
"team": "5d4c412caf60c707c225565d",
"__v": 0
},
{
"createAt": "2019-08-08T15:37:45.028Z",
"_id": "5d4c42242a822207ecb315e9",
"userName": "모하메드 살라",
"userNum": 11,
"position": "공격수",
"team": "5d4c412caf60c707c225565e",
"userMail": "이집트메시@리버풀.com",
"__v": 0
},
{
"createAt": "2019-08-08T15:37:45.028Z",
"_id": "5d4c42242a822207ecb315ea",
"userName": "피에르에메릭 오바메양",
"userNum": 17,
"position": "공격수",
"team": "5d4c412caf60c707c225565f",
"userMail": "득점왕@아스날.com",
"__v": 0
},
{
"createAt": "2019-08-08T15:37:45.028Z",
"_id": "5d4c42242a822207ecb315eb",
"userName": "메수트 외질",
"userNum": 11,
"position": "미드필더",
"team": "5d4c412caf60c707c225565f",
"userMail": "노랑머리@아스날.com",
"__v": 0
}
]

where 조건이 뭐뭐 잇는지 적어두자.

  • $eq는 ==
  • $gt는 >
  • $gte는 >=
  • $in은 Array타입 시 해당 데이터가 안에 포함되엇는지
  • $lt는 <
  • $lte는 <=
  • $ne는 !=
  • $nin은 Array타입 시 해당 데이터가 안에 포함 안되어잇는지

조건절은 다 일일히 적긴 귀찬으니까 하나만 예로.

1
2
3
4
5
6
7
8
9
10
11
12
// @TODO: 조건절 예시 아래는 or
router.get('/or', (req, res) => {
userModel.find({
$or: [{ userName: '메수트 외질' }, { userName: '크리스티아누 날강두' }]
}).exec((err, data) => {
if (err) {
res.json(err);
} else {
res.json(data);
}
});
});

결과는 아래와 같음

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
[
{
"createAt": "2019-08-08T15:37:45.028Z",
"_id": "5d4c41cc2a822207ecb315e8",
"userName": "크리스티아누 날강두",
"userNum": 7,
"position": "공격수",
"userMail": "날강두@유벤투스.com",
"team": "5d4c412caf60c707c225565d",
"__v": 0
},
{
"createAt": "2019-08-08T15:37:45.028Z",
"_id": "5d4c42242a822207ecb315eb",
"userName": "메수트 외질",
"userNum": 11,
"position": "미드필더",
"team": "5d4c412caf60c707c225565f",
"userMail": "노랑머리@아스날.com",
"__v": 0
}
]

특정 필드만 받을 경우는 아래 처럼 하면 된다.

1
2
3
4
5
6
7
8
9
10
11
// @TODO: 특정 필드만 읽고 싶을 때.
router.get('/find-attr', (req, res) => {
userModel.find({}).select('userName userMail')
.exec((err, data) => {
if (err) {
res.json(err);
} else {
res.json(data);
}
});
});

아 sql의 select username, userMail from … 뭐 이런 거.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
[
{
"_id": "5d4c41cc2a822207ecb315e8",
"userName": "크리스티아누 날강두",
"userMail": "날강두@유벤투스.com"
},
{
"_id": "5d4c42242a822207ecb315e9",
"userName": "모하메드 살라",
"userMail": "이집트메시@리버풀.com"
},
{
"_id": "5d4c42242a822207ecb315ea",
"userName": "피에르에메릭 오바메양",
"userMail": "득점왕@아스날.com"
},
{
"_id": "5d4c42242a822207ecb315eb",
"userName": "메수트 외질",
"userMail": "노랑머리@아스날.com"
}
]

sql의 like 같은 기능이 mongo에선 regex다.

1
2
3
4
5
6
7
8
9
10
// @TODO: 조건으로 정규식 사용, sql like과 같은 기능.
router.get('/regex', (req, res) => {
userModel.find({ userName: { $regex: new RegExp('날강두', 'i') } }).exec((err, data) => {
if (err) {
res.json(err);
} else {
res.json(data);
}
});
});

날강두가 포함한 이름을 검색하는건데 아직 자주 안써봐서 좀.. like문도 기억이 잘..

mongodb regex

필요할 땐 공식 문서를 번역기로 돌려보자 암튼 결과는 이러하다.

1
2
3
4
5
6
7
8
9
10
11
12
[
{
"createAt": "2019-08-08T15:37:45.028Z",
"_id": "5d4c41cc2a822207ecb315e8",
"userName": "크리스티아누 날강두",
"userNum": 7,
"position": "공격수",
"userMail": "날강두@유벤투스.com",
"team": "5d4c412caf60c707c225565d",
"__v": 0
}
]

리턴 받는 개수를 정해서 받고 싶으면 아래 처럼 하면 된다.

1
2
3
4
5
6
7
8
9
10
// @TODO: 개수 제한.
router.get('/limit', (req, res) => {
userModel.find({}).limit(2).exec((err, data) => {
if (err) {
res.json(err);
} else {
res.json(data);
}
});
});

sql에서 select top 2 … 또는 select * … limit 2; 이런 거

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
[
{
"createAt": "2019-08-08T15:37:45.028Z",
"_id": "5d4c41cc2a822207ecb315e8",
"userName": "크리스티아누 날강두",
"userNum": 7,
"position": "공격수",
"userMail": "날강두@유벤투스.com",
"team": "5d4c412caf60c707c225565d",
"__v": 0
},
{
"createAt": "2019-08-08T15:37:45.028Z",
"_id": "5d4c42242a822207ecb315e9",
"userName": "모하메드 살라",
"userNum": 11,
"position": "공격수",
"team": "5d4c412caf60c707c225565e",
"userMail": "이집트메시@리버풀.com",
"__v": 0
}
]

조회 햇을 시 해당 숫자만큼 건너뛰려면 아래 처럼 하면 된다.

1
2
3
4
5
6
7
8
9
10
// @TODO: 해당 숫자만큼 건너뛰고 조회 결과 리턴.
router.get('/skip', (req, res) => {
userModel.find({}).skip(2).exec((err, data) => {
if (err) {
res.json(err);
} else {
res.json(data);
}
});
});

글쎄 이거 페이징 구현할 때 말곤 써 본 기억이 업다

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
[
{
"createAt": "2019-08-08T15:37:45.028Z",
"_id": "5d4c42242a822207ecb315ea",
"userName": "피에르에메릭 오바메양",
"userNum": 17,
"position": "공격수",
"team": "5d4c412caf60c707c225565f",
"userMail": "득점왕@아스날.com",
"__v": 0
},
{
"createAt": "2019-08-08T15:37:45.028Z",
"_id": "5d4c42242a822207ecb315eb",
"userName": "메수트 외질",
"userNum": 11,
"position": "미드필더",
"team": "5d4c412caf60c707c225565f",
"userMail": "노랑머리@아스날.com",
"__v": 0
}
]

아 정렬이 업군

1
2
3
4
5
6
7
8
9
10
// @TODO: 정렬.
router.get('/sort', (req, res) => {
userModel.find({}).sort({ id: -1 }).exec((err, data) => {
if (err) {
res.json(err);
} else {
res.json(data);
}
});
});

필드: 1이면 오름차순, 필드: -1이면 내림차순이다.

정렬 할 필드 기준이 여러 개면 {필드: 1, 필드: -1 …} 이런식으로 추가를 하면 되는데

정렬 순도 역시 적힌대로 앞에 꺼 부터 먼저 정렬 후 그 뒤에 다음 정렬임.

아 조건에 맞게 조회했을 때 첫 번째 문서만 조회하는 기능이 따로 잇다.

1
2
3
4
5
6
7
8
9
10
// @TODO: 조건에 맞는 데이터 조회 해서 첫 번째 문서만 리턴.
router.get('/findOne', (req, res) => {
userModel.findOne({ userName: '크리스티아누 날강두' }, (err, data) => {
if (err) {
res.json(err);
} else {
res.json(data);
}
});
});

특이점은 find류는 다 Array 안에 Object가 포함이지만 얘는 단독 Object로 넘어옴.

1
2
3
4
5
6
7
8
9
10
{
"createAt": "2019-08-08T15:37:45.028Z",
"_id": "5d4c41cc2a822207ecb315e8",
"userName": "크리스티아누 날강두",
"userNum": 7,
"position": "공격수",
"userMail": "날강두@유벤투스.com",
"team": "5d4c412caf60c707c225565d",
"__v": 0
}

엇 글이 너무 길어진다. 나눠서 내일 2편을 작성 하자.