8.1 NoSQL & SQL
1) NoSQL
- MySQL같은 SQL 데이터베이스와는 다른 유형의 데이터
- NoSQL의 대표주자인 mongoDB 사용
- JOIN : 관계가 있는 테이블끼리 데이터를 합치는 기능
- 빅데이터, 메시징, 세션 관리 등(비정형 데이터)에는 몽고디비 사용하면 좋음
SQL(MySQL) | NoSQL(mongoDB) |
---|
규칙에 맞는 데이터 입력 | 자유로운 데이터 입력 |
테이블 간 JOIN 지원 | 컬렉션 간 JOIN 미지원 |
안정성, 일관성 | 확장성, 가용성 |
용어(테이블, 로우, 컬럼) | 용어(컬렉션, 다큐먼트, 필드) |
8.2 몽고디비, 컴파스 설치하기
8.3 데이터베이스, 컬렉션 생성하기
1) 데이터베이스 생성하기
admin> use nodejs;
switched to db nodejs
nodejs> show dbs;
admin 132.00 KiB
config 60.00 KiB
local 72.00 KiB
nodejs> db
nodejs
2) 컬렉션 생성하기
8.4 CRUD 작업하기
1) Create
nodejs> db.users.insertOne({ name:'zero', age: 24, married: false, comment: '안녕하세요. 간단히 몽고디비 사용 방법에 대 해 알아봅시다.', createdAt: new Date() });
{
acknowledged: true,
insertedId: ObjectId("63d10efa2f74d5a76f57b92b")
}
nodejs> db.users.insertOne({ name:'nero', age: 32, married: true, comment: '안녕하세요. zero 친구 nero입니다.', createdAt: new Date() })
{
acknowledged: true,
insertedId: ObjectId("63d10f3d2f74d5a76f57b92c")
}
nodejs> db.comments.insertOne({ commenter: ObjectId("63d10efa2f74d5a76f57b92b"), comment: '안녕하세요. zero의 댓글입니다', createdAt: new Date() });
{
acknowledged: true,
insertedId: ObjectId("63d10ff22f74d5a76f57b92d")
}
2) Read
- find로 조회. findOne으로 하나만 조회
nodejs> db.users.find({});
[
{
_id: ObjectId("63d10efa2f74d5a76f57b92b"),
name: 'zero',
age: 24,
married: false,
comment: '안녕하세요. 간단히 몽고디비 사용 방법에 대해 알아봅시다.',
createdAt: ISODate("2023-01-25T11:14:02.575Z")
},
{
_id: ObjectId("63d10f3d2f74d5a76f57b92c"),
name: 'nero',
age: 32,
married: true,
comment: '안녕하세요. zero 친구 nero입니다.',
createdAt: ISODate("2023-01-25T11:15:09.107Z")
}
]
nodejs> db.comments.find({});
[
{
_id: ObjectId("63d10ff22f74d5a76f57b92d"),
commenter: ObjectId("63d10efa2f74d5a76f57b92b"),
comment: '안녕하세요. zero의 댓글입니다',
createdAt: ISODate("2023-01-25T11:18:10.957Z")
}
]
- 두 번째 인수로 조회할 필드를 선택할 수 있음(1은 추가, 0은 제외)
nodejs> db.users.find({}, { _id: 0, name: 1, married: 1});
[ { name: 'zero', married: false }, { name: 'nero', married: true } ]
nodejs> db.users.find({age: {$gt: 30}, married: true}, {_id: 0, name: 1, age: 1});
[ { name: 'nero', age: 32 } ]
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은 오름차순 )
nodejs> db.users.find({}, {_id: 0, name: 1, age: 1}).sort({age: -1});
[ { name: 'nero', age: 32 }, { name: 'zero', age: 24 } ]
- limit 메서드로 조회할 다큐먼트 개수 제한
nodejs> db.users.find({}, {_id: 0, name: 1, age: 1}).sort({age: -1}).limit(1);
[ { name: 'nero', age: 32 } ]
nodejs> db.users.find({}, {_id: 0, name: 1, age: 1}).sort({age: -1}).limit(1).skip(1);
[ { name: 'zero', age: 24 } ]
3) Update
- updateOne, updateMany 메서드로 쿼리
- 첫 번째 인수로 수정 대상을, 두 번째 인수로 수정 내용을 제공
- $set을 붙이지 않으면 다큐먼트 전체가 대체되므로 주의
- 결과로 찾은 개수, 수정된 개수 등이 나옴
nodejs> db.users.updateOne({ name: 'nero'}, { $set: { comment: '안녕하세요. 이 필드를 바꿔보겠습니다!'}});
{
acknowledged: true,
insertedId: null,
matchedCount: 1,
modifiedCount: 1,
upsertedCount: 0
}
4) Delete
- deleteOne, deleteMany 메서드로 쿼리
- 첫 번째 인수로 삭제할 대상 조건 제공
- 성공 시 삭제된 개수가 반환됨
nodejs> db.users.deleteOne({name: 'nero'});
{ acknowledged: true, deletedCount: 1 }
8.5 몽구스 사용하기
1) 몽구스 ODM
- 몽고디비 작업을 쉽게 할 수 있도록 도와주는 라이브러리
- ODM: Object Document Mapping: 객체와 다큐먼트를 매핑(1대1)
- 몽고디비에 없어 불편한 기능들을 몽구스가 보완
- 테이블과 유사한 기능, JOIN 기능 추가
- 프로젝트 세팅 후, 콘솔을 통해 경로로 이동한 후 package.json 설치
{
"name": "learn-mongoose",
"version": "0.0.1",
"description": "몽구스를 배우자",
"main": "app.js",
"scripts": {
"start": "nodemon app"
},
"author": "ZeroCho",
"license": "MIT",
"dependencies": {
"express": "^4.18.2",
"mongoose": "^6.8.4",
"morgan": "^1.10.0",
"nunjucks": "^3.2.3"
},
"devDependencies": {
"nodemon": "^2.0.20"
}
}
$ npm i express morgan nunjucks mongoose
$ npm i -D nodemon
2) 몽고디비 연결하기
- 몽구스를 통해 몽고디비와 연결하기
- 인증은 admin 데이터베이스에서, 서비스는 dbName 데이터베이스에서
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;
3) 앱과 연결하기
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);
});
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'), '번 포트에서 대기 중');
});
4) 스키마 정의하기
- schemas 폴더 안에 작성
- MySQL의 테이블처럼 정해진 데이터만 들어갈 수 있게 강제함
- type은 자료형, require는 필수 여부, default는 기본값, unique는 고유 여부
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);
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);
5) 라우터 작성하기
- 프론트엔드 코드는 서버에 요청을 보내는 AJAX 요청 위주로
- 서버 코드는 응답을 보내는 라우터 위주로 살펴보기
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;
6) 사용자 라우터
- router.get, post, put, patch, delete 라우터 작성
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;
7) 댓글 라우터
- router.get, post, put, patch, delete 라우터 작성
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;
8) 라우터 연결하기
const connect = require('./schemas');
const indexRouter = require('./routes/index');
const usersRouter = require('./routes/users');
const commentsRouter = require('./routes/comments');
const app = express();
...
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} 라우터가 없습니다.`);
...
9) 서버 연결하기
- npm start 후 localhost:3002에 접속