기존 블로그에 작성했던 포스트를 이전한 글입니다.
해당 포스트는
NODEJS
를 학습하며 정리한 내용에 대한 포스트입니다.
🌈 몽고디비
💻 NoSQL vs SQL
MySQL같은 SQL 데이터베이스와는 다른 유형의 데이터
NoSQL의 대표주자인 mongoDB(몽고디비) 사용
JOIN: 관계가 있는 테이블끼리 데이터를 합치는 기능(몽고디비 aggregate로 흉내 가능)
빅데이터, 메시징, 세션 관리 등(비정형 데이터)에는 몽고디비 사용하면 좋음
SQL(MySQL) | NoSQL(몽고디비) |
---|---|
규칙에 맞는 데이터 입력 | 자유로운 데이터 입력 |
테이블 간 JOIN 지원 | 컬렉션 간 JOIN 미지원 |
안정성, 일관성 | 확장성, 가용성 |
용어(테이블, 로우, 컬럼) | 용어(컬렉션, 다큐먼트, 필드) |
💻 몽고디비 설치하기
몽고디비 공식 사이트에서 내려받을 수 있습니다.
🍳 몽고디비 연결하기
윈도의 경우 C:\에 data 폴더를 만들고 그 안에 db 폴더를 만듦
콘솔로 몽고디비가 설치된 경로(기본적으로 C:\Program Files\MongoDB\Server\6.0\bin)로 이동해 몽고디비를 실행
1
mongod --ipv6
mongod 가 실행 중인 상태에서 mongosh 실행
🍳 어드민 설정하기
어드민 권한을 설정하여 디비에 비밀번호 걸기
1
2
3
4
test> use admin
switched to db admin
admin> db.createUser({ user: '이름', pwd: '비밀번호', roles: ['root'] })
Successfully added user: { "user" : "root", "roles" : [ "root" ] }
mongod를 입력했던 콘솔을 종료한 후 mongod --ipv6 --auth
명령어로 접속
--auth
는 로그인이 필요하다는 뜻
mongosh 콘솔도 종료한 후 mongosh admin -u 이름 -p 비밀번호
명령어로 접속
1
2
3
4
5
6
7
$ mongosh admin -u [이름] -p [비밀번호]
Current Mongosh Log ID: 62f9d501a04fc0bcbc7f2791
Connecting to: mongodb://<credentials>@127.0.0.1:27017/admin?directConnection=true&serverSelectionTimeoutMS=2000&appName=mongosh+1.5.4
Using MongoDB: 6.0.0
Using Mongosh: 1.5.4
(생략)
admin>
방금 입력한 명령어는 이름과 비밀번호가 잘 생성되었는지 확인하기 위한 것이었으므로, 앞으로 몽고디비 프롬프트를 이용할 때는 단순히 mongosh만 입력하면 된다.
💻 COMPASS
컴퍼스도 몽고디비 공식 사이트에서 다운받을 수 있다.
🍳 커넥션 생성하기
컴퍼스(MongoDB Compass Community)로 접속
Authentication 을 Username/Password로 변경, 몽고디비 계정 이름과 비밀번호 입력
💻 데이터베이스 및 컬렉션 생성하기
몽고디비 프롬프트에 접속한 후 진행하면 된다.
데이터베이스를 만드는 명령어는 use [데이터베이스명]
입니다.
1
2
> use nodejs
switched to db nodejs
데이터베이스 목록을 확인하는 명령어는 show dbs
1
2
3
4
> show dbs
admin 0.000GB
config 0.000GB
local 0.000GB
방금 생성한 nodejs가 없지만 당황하지 말자.
데이터를 최소 한 개 이상 넣어야 목록에 표시된다.
현재 사용 중인 데이터베이스를 확인하는 명령어는 db
1
2
> db
nodejs
비록 데이터베이스 목록에는 없지만, 현재 nodejs 데이터베이스를 사용하고 있음을 확인할 수 있다.
컬렉션은 따로 생성할 필요가 없다. 다큐먼트를 넣는 순간 컬렉션도 자동으로 생성된다. 하지만 다음과 같이 직접 컬렉션을 생성하는 명령어가 있다.
1
2
3
4
> db.createCollection('users')
{ "ok" : 1 }
> db.createCollection('comments')
{ "ok" : 1 }
생성한 컬렉션 목록을 확인 show collections
1
2
3
> show collections
comments
users
💻 CRUD 작업하기
🍳 Create
몽고디비는 컬럼을 정의하지 않아도 됨
자유로움이 장점, 무엇이 들어올지 모른다는 단점
자바스크립트의 자료형을 따름(차이점도 존재)
ObjectId
: 몽고디비의 자료형으로 고유 아이디 역할을 함save method로 저장
1
2
3
4
5
6
7
8
9
10
11
12
13
$ mongosh
test> use nodejs;
switched to db nodejs
nodejs> db.users.insertOne({ name: 'zero', age: 24, married: false, comment: '안녕하세요. 간단히 몽고디비 사용 방법에 대해 알아봅시다.', createdAt: new Date() });
{
acknowledged: true,
insertedId: ObjectId("5a1687007af03c3700826f70")
}
nodejs> db.users.insertOne({ name: 'nero', age: 32, married: true, comment: '안녕하세요. zero 친구 nero입니다.', createdAt: new Date() });
{
acknowledged: true,
insertedId: ObjectId("62fba0deb068d84d69d7c740")
}
db.컬렉션명.insertOne(다큐먼트)
로 다큐먼트를 생성할 수 있다.
자바스크립트 객체처럼 생성하면 된다.
🍳 Create(관계 설정)
컬렉션 간 관계를 강요하는 제한이 없으므로 직접 ObjectId를 넣어 연결
사용자의 ObjectId를 찾은 뒤 댓글 컬렉션에 넣음
🍳 Read
find로 모두 조회, findOne으로 하나만 조회
1
2
3
4
5
6
7
nodejs> db.users.find({});
[
{ "_id" : ObjectId("5a1687007af03c3700826f70"), "name" : "zero", "age" : 24, "married" : false, "comment" : "안녕하세요. 간단히 몽고디비 사용 방법을 알아봅시다.", "createdAt" : ISODate("2022-04-30T05:00:00Z") },
{ "_id" : ObjectId("5a16877b7af03c3700826f71"), "name" : "nero", "age" : 32, "married" : true, "comment" : "안녕하세요. zero 친구 nero입니다.", "createdAt" : ISODate("2017-11-23T01:00:00Z") }
]
nodejs> db.comments.find({})
[ { "_id" : ObjectId("5a1687e67af03c3700826f73"), "commenter" : ObjectId("5a1687007af03c3700826f70"), "comment" : "안녕하세요. zero의 댓글입니다.", "createdAt" : ISODate("2022-04-30T05:30:00Z") } ]
find({})는 컬렉션 내의 모든 다큐먼트를 조회하라는 뜻
🍳 Read(조건)
두 번째 인수로 조회할 필드를 선택할 수 있음(1은 추가, 0은 제외)
1
2
3
4
5
nodejs> db.users.find({}, { _id: 0, name: 1, married: 1 });
[
{ "name" : "zero", "married" : false },
{ "name" : "nero", "married" : true }
]
첫 번째 인수로 조회 조건 입력 가능
$gt나 $or같은 조건 연산자 사용
$gt
(초과), $gte
(이상), $lt
(미만), $lte
(이하), $ne
(같지 않음), $or
(또는), $in
(배열 요소 중 하나) 등이 있다.
1
2
nodejs> db.users.find({ age: { $gt: 0 }, married: true }, { _id: 0, name: 1, age: 1 });
[ { "name" : "nero", "age" : 32 } ]
1
2
3
4
5
nodejs> db.users.find({ $or: [{ age: { $gt: 30 } }, { married: false }] }, { _id: 0, name: 1, age: 1 });
[
{ "name" : "zero", "age" : 24 },
{ "name" : "nero", "age" : 32 }
]
정렬은 sort 메서드로 함
-1
은 내림차순, 1
은 오름차순
1
2
3
4
5
nodejs> db.users.find({}, { _id: 0, name:f, age: 1}).sort({ age: -1 })
[
{ "name" : "nero", "age" : 32 },
{ "name" : "zero", "age" : 24 }
]
limit 메서드로 조회할 다큐먼트 개수 제한
1
2
nodejs> db.users.find({}, { _id: 0, name: 1, age: 1 }).sort({ age: -1 }).limit(1)
[ { "name" : "nero", "age" : 32 } ]
skip 메서드로 건너뛸 다큐먼트 개수 제공
1
2
nodejs> db.users.find({}, { _id: 0, name: 1, age: 1 }).sort({ age: -1 }).limit(1).skip(1)
[ { "name" : "zero", "age" : 24 } ]
🍳 Update
첫 번째 객체는 수정할 다큐먼트를 지정하는 객체
두 번째 객체는 수정할 내용을 입력하는 객체
$set
을 붙이지 않으면 다큐먼트 전체가 대체되므로 주의
하나만 수정할 때 updateOne, 여러 개 수정할 때 updateMany
1
2
3
4
5
6
7
8
nodejs> db.users.updateOne({ name: 'nero' }, { $set: { comment: '안녕하세요 이 필드를 바꿔보겠습니다!' } });
{
acknowledged: true,
insertedId: null,
matchedCount: 1,
modifiedCount: 0,
upsertedCount: 0
}
🍳 Delete
하나만 수정할 때 deleteOne, 여러 개 수정할 때 deleteMany
첫 번째 인수로 삭제할 대상 조건 제공
성공 시 삭제된 개수가 반환됨
1
2
nodejs> db.users.deleteOne({ name: 'nero' })
{ acknowledged: true, deletedCount: 1 }
💻 몽구스 사용하기
🍳 몽구스 ODM
몽고디비 작업을 쉽게 할 수 있도록 도와주는 라이브러리
ODM
(Object Document Mapping): 객체와 다큐먼트를 매핑(1대1 짝지음)
몽고디비에 없어 불편한 기능들을 몽구스가 보완
테이블과 유사한 기능, JOIN 기능 추가
- 프로젝트 세팅 후, 콘솔을 통해 경로로 이동한 후 package.json 설치
🔻 pakage.json
1
2
3
4
5
6
7
8
9
10
11
{
"name": "learn-mongoose",
"version": "0.0.1",
"description": "몽구스를 배우자",
"main": "app.js",
"scripts": {
"start": "nodemon app"
},
"author": "ZeroCho",
"license": "MIT"
}
설치
1
2
$ npm i express morgan nunjucks mongoose
$ npm i -D nodemon
🍳 몽고디비 연결하기
몽구스를 통해 몽고디비 연결하기
- 인증은 admin 데이터베이스에서, 서비스는 dbName 데이터베이스에서
🔻scehmas/index.js
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
const mongoose = require('mongoose');
const connect = () => {
if (process.env.NODE_ENV !== 'production') {
mongoose.set('debug', true);
}
mongoose.connect('mongodb://root:nodejsbook@localhost:27017/admin', {
dbName: 'nodejs',
useNewUrlParser: true,
useCreateIndex: true,
}, (error) => {
if (error) {
console.log('몽고디비 연결 에러', error);
} else {
console.log('몽고디비 연결 성공');
}
});
};
mongoose.connection.on('error', (error) => {
console.error('몽고디비 연결 에러', error);
});
mongoose.connection.on('disconnected', () => {
console.error('몽고디비 연결이 끊겼습니다. 연결을 재시도합니다.');
connect();
});
module.exports = connect;
🍳 앱과 연결하기
app.js로 연결
schemas/index.js의 함수가 실행됨
mongoose.connect 함수가 몽고디비에 연결을 시도
mongoose.set은 디버깅 모드(모드를 켰을 때 콘솔에 쿼리가 찍힘)
연결이 끊기면(disconnection) 다시 연결을 시도
🔻app.js
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
const express = require('express');
const path = require('path');
const morgan = require('morgan');
const nunjucks = require('nunjucks');
const connect = require('./schemas');
const app = express();
app.set('port', process.env.PORT || 3002);
app.set('view engine', 'html');
nunjucks.configure('views', {
express: app,
watch: true,
});
connect();
app.use(morgan('dev'));
app.use(express.static(path.join(__dirname, 'public')));
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use((req, res, next) => {
const error = new Error(`${req.method} ${req.url} 라우터가 없습니다.`);
error.status = 404;
next(error);
});
app.use((err, req, res, next) => {
res.locals.message = err.message;
res.locals.error = process.env.NODE_ENV !== 'production' ? err : {};
res.status(err.status || 500);
res.render('error');
});
app.listen(app.get('port'), () => {
console.log(app.get('port'), '번 포트에서 대기 중');
});
🍳 스키마 정의하기
schemas 폴더 안에 작성
MySQL의 테이블처럼 정해진 데이터만 들어갈 수 있게 강제함
type은 자료형, require는 필수 여부 default는 기본값, unique는 고유 여부
🔻schemas/user.js
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
const mongoose = require('mongoose');
const { Schema } = mongoose;
const userSchema = new Schema({
name: {
type: String,
required: true,
unique: true,
},
age: {
type: Number,
required: true,
},
married: {
type: Boolean,
required: true,
},
comment: String,
createdAt: {
type: Date,
default: Date.now,
},
});
module.exports = mongoose.model('User', userSchema);
🔻schemas/comment.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
const mongoose = require('mongoose');
const { Schema } = mongoose;
const { Types: { ObjectId } } = Schema;
const commentSchema = new Schema({
commenter: {
type: ObjectId,
required: true,
ref: 'User',
},
comment: {
type: String,
required: true,
},
createdAt: {
type: Date,
default: Date.now,
},
});
module.exports = mongoose.model('Comment', commentSchema);
🍳 라우터 작성
프론트엔드 코드는 서버에 요청을 보내는 AJAX 요청 위주로
서버 코드는 응답을 보내는 라우터 위주로 살펴보기
🔻routes.index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const express = require('express');
const User = require('../schemas/user');
const router = express.Router();
router.get('/', async (req, res, next) => {
try {
const users = await User.find({});
res.render('mongoose', { users });
} catch (err) {
console.error(err);
next(err);
}
});
module.exports = router;
🍳 사용자 라우터
router.get, post, put, patch, delete 라우터 작성
🔻routes/users.js
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
43
44
const express = require('express');
const User = require('../schemas/user');
const Comment = require('../schemas/comment');
const router = express.Router();
router.route('/')
.get(async (req, res, next) => {
try {
const users = await User.find({});
res.json(users);
} catch (err) {
console.error(err);
next(err);
}
})
.post(async (req, res, next) => {
try {
const user = await User.create({
name: req.body.name,
age: req.body.age,
married: req.body.married,
});
console.log(user);
res.status(201).json(user);
} catch (err) {
console.error(err);
next(err);
}
});
router.get('/:id/comments', async (req, res, next) => {
try {
const comments = await Comment.find({ commenter: req.params.id })
.populate('commenter');
console.log(comments);
res.json(comments);
} catch (err) {
console.error(err);
next(err);
}
});
module.exports = router;
🍳 댓글 라우터
router.get, post, put, patch, delete 라우터 작성
🔻routes/comment.js
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
43
44
45
const express = require('express');
const Comment = require('../schemas/comment');
const router = express.Router();
router.post('/', async (req, res, next) => {
try {
const comment = await Comment.create({
commenter: req.body.id,
comment: req.body.comment,
});
console.log(comment);
const result = await Comment.populate(comment, { path: 'commenter' });
res.status(201).json(result);
} catch (err) {
console.error(err);
next(err);
}
});
router.route('/:id')
.patch(async (req, res, next) => {
try {
const result = await Comment.update({
_id: req.params.id,
}, {
comment: req.body.comment,
});
res.json(result);
} catch (err) {
console.error(err);
next(err);
}
})
.delete(async (req, res, next) => {
try {
const result = await Comment.remove({ _id: req.params.id });
res.json(result);
} catch (err) {
console.error(err);
next(err);
}
});
module.exports = router;
🍳 라우터 연결하기
app.js에 연결
🔻app.js
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
const express = require('express');
const path = require('path');
const morgan = require('morgan');
const nunjucks = require('nunjucks');
const connect = require('./schemas');
const indexRouter = require('./routes/index');
const usersRouter = require('./routes/users');
const commentsRouter = require('./routes/comments');
const app = express();
app.set('port', process.env.PORT || 3002);
app.set('view engine', 'html');
nunjucks.configure('views', {
express: app,
watch: true,
});
connect();
app.use(morgan('dev'));
app.use(express.static(path.join(__dirname, 'public')));
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use('/', indexRouter);
app.use('/users', usersRouter);
app.use('/comments', commentsRouter);
app.use((req, res, next) => {
const error = new Error(`${req.method} ${req.url} 라우터가 없습니다.`);
error.status = 404;
next(error);
});
...
🍳 서버 연결하기
npm start 후 localhost:3002에 접속
콘솔에 찍히는 몽고디비 쿼리 확인
1
2
3
4
5
6
7
8
9
10
11
12
$ npm start
> learn-mongoose@0.0.1 start
> nodemon app
[nodemon] 2.0.16
[nodemon] to restart at any time, enter `rs`
[nodemon] watching: *.*
[nodemon] watching extensions: js,mjs,json
[nodemon] starting `node app.js`
3002 번 포트에서 대기 중
몽고디비 연결 성공
Mongoose: users.createIndex({ name: 1 }, { unique: true, background: true })
💻 함께 보면 좋은 자료
📚 레퍼런스
조현영. Node.js 교과서 = Node.js Textbook / 조현영 지음 (2022). Print.