8.1 NoSQL vs SQL
⇒ NoSQL의 대표주자인 mongoDB(몽고디비) 사용
SQL(MySQL) | NoSQL |
규칙에 맞는 데이터 입력 | 자유로운 데이터 입력 |
안정성, 일관성 | 확장성, 가용성 |
테이블 간 JOIN 지원 | 테이블 간 JOIN 미지원 |
용어(테이블, 컬럼, 로우) | 용어(컬렉션, 필드, 다큐먼트) |
- JOIN : 관계가 있는 테이블끼리 데이터를 합치는 기능이다. (몽고디비는 aggregate로 흉내는 가능하다)
- 빅데이터, 메시징, 세션관리 등(비정형데이터)에는 몽고디비를 사용하면 좋다.
8.2 데이터베이스와 컬렉션 만들기
1. 데이터베이스 생성하기
admin> use nodejs;
switched to db nodejs
nodejs> show dbs;
admin 132.00 KiB
config 60.00 KiB
local 72.00 KiB
2. 컬렉션 생성하기
nodejs> db.createCollection('users')
{ ok: 1 }
nodejs> db.createCollection('comments')
{ ok: 1 }
nodejs> show dbs
admin 132.00 KiB
config 60.00 KiB
local 72.00 KiB
nodejs 16.00 KiB
8.3 CRUD 작업하기
1. create
⇒ 몽고디비는 컬럼을 정의하지 않아도 된다.
- 자유로움이 장점이지만 무엇이 들어올지 모른다는 단점도 존재한다.
- 자바스크립트의 자료형을 따른다(차이점도 존재)
- ObjectId: 몽고디비의 자료형으로 고유 아이디(PK) 역할을 한다.
- insertOne method로 저장한다.
nodejs> db.users.insertOne({name:'zero', age:24, married:false, comment:'안녕하세요', createAt : new Date()});
{
acknowledged: true,
insertedId: ObjectId('659a9933dd7b2316d1903de4')
}
nodejs> db.users.insertOne({name:'jaeik', age:39, married:true, comment:'안녕하세요. 김재익입니다.', createAt : new Date()});
{
acknowledged: true,
insertedId: ObjectId('659a9966dd7b2316d1903de5')
}
2. Create(관계 설정)
⇒ 컬렉션 간 관계를 강요하는 제한이 없으므로 직접 ObjectId를 넣어 연결해야 한다.
- 사용자의 ObjectId를 찾아 댓글 컬렉션에 넣는다.
nodejs> db.comments.insertOne({ commenter : ObjectId('659a9933dd7b2316d1903de4'), comment: '댓글을 달아봅시다'});
{
acknowledged: true,
insertedId: ObjectId('659a99dcdd7b2316d1903de6')
}
3. Read
⇒ find로 조회, findOne으로 하나만 조회힌다.
nodejs> db.users.find({});
[
{
_id: ObjectId('659a9933dd7b2316d1903de4'),
name: 'zero',
age: 24,
married: false,
comment: '안녕하세요',
createAt: ISODate('2024-01-07T12:29:39.641Z')
},
{
_id: ObjectId('659a9966dd7b2316d1903de5'),
name: 'jaeik',
age: 39,
married: true,
comment: '안녕하세요. 김재익입니다.',
createAt: ISODate('2024-01-07T12:30:30.025Z')
}
]
nodejs> db.comments.find({});
[
{
_id: ObjectId('659a99dcdd7b2316d1903de6'),
commenter: ObjectId('659a9933dd7b2316d1903de4'),
comment: '댓글을 달아봅시다'
}
]
4. Read(조건)
⇒ 두 번째 인수로 조회할 필드를 선택할 수 있다.(1은 추가, 0은 제외)
nodejs> db.users.find({},{ _id:0, name:1, married:1});
[ { name: 'zero', married: false }, { name: 'jaeik', married: true } ]
⇒ 첫 번째 인수로 조회조건 입력이 가능하다.
- $gt, $or 같은 조건 연산자를 사용할 수 있다.
nodejs> db.users.find({ age : { $gt : 30 }, married : true},{ _id:0, name:1, married:1});
[ { name: 'jaeik', married: true } ]
nodejs> db.users.find({ $or : [{age : { $gt : 30 }}, {married : false}]},{ _id:0, name:1, married:1});
[ { name: 'zero', married: false }, { name: 'jaeik', married: true } ]
5. Read(조건)
⇒ 정렬은 sort 메서드로 한다.(1: 오름차순, -1: 내림차순)
nodejs> db.users.find({},{ _id:0, name:1, age:1}).sort({age : -1});
[{ name: 'jaeik', age: 39 }, { name: 'zero', age: 24 } ]
⇒ limit 메서드로 조회할 다큐먼트 개수를 제한한다.
nodejs> db.users.find({},{ _id:0, name:1, age:1}).sort({age : -1}).limit(1)
[ { name: 'jaeik', age: 39 } ]
⇒ skip 메서드로 건너뛸 다큐먼트 개수를 설정한다.
nodejs> db.users.find({},{ _id:0, name:1, age:1}).sort({age : -1}).limit(1).skip(1)
[ { name: 'zero', age: 24 } ]
6. update
⇒ updateOne, updateMany 메서드로 쿼리를 수행한다.
- 첫 번째 인수로 수정대상을, 두 번째 인수로 수정 내용을 제공한다.
- 두 번재 인수의 시작으로 $set을 붙이지 않으면 다큐먼트 전체가 대체되므로 주의해야 한다.
- 결과로 찾은 개수, 수정된 개수 등의 정보가 표시된다.
nodejs> db.users.updateOne({name:'jaeik'}, {$set : {comment : '안녕하세요. 이 필드를 바꾸겠습니다.'}});
{
acknowledged: true,
insertedId: null,
matchedCount: 1,
modifiedCount: 1,
upsertedCount: 0
}
7. delete
⇒ deleteOne, deleteMany 메서드로 쿼리를 수행한다.
- 첫 번재 인수로 삭제할 대상 조건을 제공한다.
- 성공 시 삭제된 개수가 반환된다.
nodejs> db.users.deleteOne({name:'zero'});
{ acknowledged: true, deletedCount: 1 }
nodejs> db.users.find({});
[
{
_id: ObjectId('659a9966dd7b2316d1903de5'),
name: 'jaeik',
age: 39,
married: true,
comment: '안녕하세요. 이 필드를 바꾸겠습니다.',
createAt: ISODate('2024-01-07T12:30:30.025Z')
}
]
8.5 몽구스 사용하기
1. 몽구스 ODM
⇒ 몽고디비 작업을 쉽게 할 수 있도록 도와주는 라이브러리이다.
- ODM : Object Document Mapping : 객체와 다큐먼트를 매핑한다(1:1로 짝지음)
- 몽구스는 mySQL과 다르게 몽고디비 드라이버를 안 받아도 된다.(몽구스 안에 드라이버가 내장)
2. 몽고디비 연결하기
⇒ 몽구스를 통해 몽고디비 연결하기
- 인증은 admin 데이터베이스에서, 서비스는 dbName 데이터베이스에서 한다.
const mongoose = require('mongoose');
const connect = () => {
if(process.env.NODE_ENV !== 'production') {
mongoose.set('debug', true);
}
mongoose.connect('mongodb://root:0000@localhost:27017/admin', {
dbName : 'nodejs',
}).then(() =>{
console.log('몽고디비 연결 성공');
}).catch((err) => {
console.error('몽고디비 연결 에러', err);
});
mongoose.connection.on('error', (error) => {
console.error('몽고디비 연결 에러', error);
});
mongoose.connection.on('disconnected', () => {
console.error('몽고디비 연결이 끊겼습니다. 연결을 재시도합니다.');
connect();
});
};
module.exports = connect;
3. 앱과 연결하기
⇒ app.js로 연결
- schemas/index.js의 함수가 실행된다.
- mongoose.connect 함수가 몽고디비에 연결을 시도한다.
- mongoose.set은 디버깅 모드(모드를 켰을 때 콘솔에 쿼리가 찍힌다)
- 연결이 끊기면(disconnection) 다시 연결을 시도한다.
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');
});
4. 스키마 작성하기
⇒ schemas 폴더 안에 생성한다.
- MySQL의 테이블처럼 정해진 데이터만 들어갈 수 있게 강제한다.
- type은 자료형, required는 필수여부, default는 기본값, unique는 고유여부
// schema/user.js
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,
createAt : {
type : Date,
default : Date.now,
},
});
module.exports = mongoose.model('User', userSchema);
// schemas/comment.js
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,
},
createAt : {
type : Date,
default : Date.now,
}
});
module.exports = mongoose.model('Comment', commentSchema);
5. 라우터 작성하기
⇒ 프론트앤드 코드는 서버에 요청을 보내는 AJAX 요청 위주로만 보는 것이 좋다.
⇒ 서버 코드는 응답을 보내는 라우터 위주로 살펴보는게 좋다.
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 {
// populate는 RDB의 JOIN의 역할을 수행한다.
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,
commnet : 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.commnet,
},);
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. 라우터 연결하기
⇒ 기존 작성해 둔 app.js에 라우터를 연결한다.
const express = require('express');
const path = require('path');
const morgan = require('morgan');
const nunjucks = require('nunjucks');
const connect = require('./schemas');
const indexRouter = require('./routes');
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'), '번 포트에서 대기중');
});
'BackEnd > Node' 카테고리의 다른 글
섹션 9. 10장 API 서버 만들기(JWT, CORS) (0) | 2024.01.17 |
---|---|
[노드교과서] 섹션 9. 노드버드 SNS 만들기 (0) | 2024.01.12 |
[노드교과서] 섹션 6. 데이터베이스 (0) | 2024.01.03 |
[노드교과서] 섹션5. 익스프레스 웹 서버 만들기 (0) | 2023.12.31 |
[노드교과서] 섹션 3. http 모듈로 서버 만들기. 섹션 4. 패키지 매니저 (0) | 2023.12.19 |