이번 포스팅에서는 1. Windows에서 MongoDB 설치 및 실행, 2. MongoDB DB 생성 및 CRUD, 3. Mongoose 쿼리 수행 예(전체 코드 설명)에 대해 다루겠다.
책 Node.js 교과서(개정 2판) 책의 8장의 내용을 참고했다.
+모든 코드는 github주소에 있다.
MongoDB?
MongoDB
- NoSQL(Not only SQL) - 관계형 데이터베이스가 아님
- 확장성과 가용성이 좋음
- 한 컬렉션에 다큐먼트로 다른 값의 데이터가 들어갈 수 있음
MySQL VS MongoDB
테이블(table) - 컬렉션(collection)
로우(row) - 다큐먼트(document)
컬럼(column) - 필드(field)
1. Windows에서 MongoDB 설치 및 실행
- 몽고디비 공식 다운 사이트에 접속한다.
On-Premises
선택 후 MongoDB Community Server 탭에서Download
를 누른다.3. 설치 방법
4. 실행
- C:\에 data 폴더를 만들고 그 안에 db폴더를 만든다.
** 이 폴더가 없으면 실행 불가C:\data\db
2. cmd
를 키고 C:\Program Files\MongoDB\Server\5.0\bin
로 이동
**5.0은 설치된 MongoDB에 따라 다를 수 있음
3. 몽고디비 실행 명령어: mongod
입력
- 가장 아래의 "msg"에 "Waiting for connections"가 입력되면 성공!
- 기본적으로 27017번 포트에서 실행됨
- 몽고디비 프롬프트 접속:
-1. cmd
를 하나 더 열고 C:\Program Files\MongoDB\Server\5.0\bin
로 이동
-2. mongo
명령어 입력
-- 프롬프트가 >
로 바뀌었다면 성공
-- Error saving history file: FileOpenFailed: Unable to open() file No such file or directory 에러도 있는데, 그냥 history file을 찾을 수 없다는 것이므로 무시하고 넘어가겠다. 해결 방법을 아시는 분이 있으면 댓글 남겨주시면 감사합니다..
5. 관리자 계정 추가
use admin
명령어- root 계정 생성:
db.createUser({ user: '사용자 이름', pwd: '비밀번호', roles: ['root'] })
mongod
를 입력했었던 콘솔을 종료(ctrl+c
)하고,mongod --auth
입력
--auth: 로그인 필요
7. mongo
를 입력했었던 콘솔을 종료(ctrl+c
)하고, **mongo admin -u [사용자 이름] -p [비밀번호]
입력
5. 커넥션(Connection) 생성 - feat.컴퍼스
** windows는 MongoDB를 설치하면서 컴퍼스가 자동으로 설치된다.
- 4(실행)-6, 4(실행)-7이 되어있는 상태에서
MongoDBCompass
프로그램 실행
New Connection
화면에서Fill in connection fields individually
클릭
-1. Authentication : Username/Password
로 변경
-2. MongoDB때 생성한 Username과 Password 입력
-3. Connect 클릭
localhost:27017
에 접속됨, 기본적으로 admin, config, local 데이터베이스가 존재
2. MongoDB DB 생성 및 CRUD 명령어
1. DB 생성
- 데이터베이스 생성, 해당 db 사용 알림:
use [데이터베이스명]
데이터베이스 목록 확인:
show dbs
*use로 생성한 nodejs가 안 보이지만 최소 한 개 이상의 데이터가 있어야 보임
현재 사용중인 데이터베이스 확인:
db
- 컬렉션 생성:
db.createCollection('[컬렉션명'])
- 컬렉션 목록 확인:
show collections
2. CRUD
C: CREATE(생성)
- 데이터 입력:
db.컬렉션명.save(다큐먼트)
데이터 생성 EXAMPLE )
db.users.save({ name: 'zero', age: 24, married: false, comment: '안녕하세요. 간단히 몽고디비 사용 방법에 대해 알아봅시다.' , createAt: new Date() });
출력(console)
R: READ(조회)
- 조건이 있는 데이터 조회:
db.users.find({});
users 조회 EXAMPLE )
- 데이터 조회:
db.[다큐먼트].find({조건}, {조회할 필드})
특정 조건으로 조회 EXAMPLE )
특정 조건에 속성 사용 EXAMPLE )
자주 쓰이는 연산자
$gt(초과), $gte(이상), $lt(미만), $lte(이하), $ne(같지 않음), $or(또는), $in(배열 요소 중 하나)EXAMPLE )
입력
db.users.find({ age: {$gt: 30}, married: true}, {_id:0, name: 1, age: 1});
출력
{ "name" : "nero", "age" : 32 }
특정 필드만 조회 EXAMPLE )
정렬, 개수 제한, 건너뛸 개수지정 조회 EXAMPLE )
**정렬: .sort([조건])
, 개수 제한: .limit([개수])
, 건너뛸 개수: .skip([개수])
U: UPDATE(수정)
- 데이터 수정:
db.컬렉션명.update({다큐먼트}, {수정할 내용})
UPDATE EXAMPLE )
db.users.update({name:'nero'}, {$set: { comment: '안녕하세요. 검은 고양이 nero 아닙니다.' }});
출력 **첫 번째 객체에 해당하는 다큐먼트 수(nMatched)와 수정된 다큐먼트 수(nModified)가 반환
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
D: DELETE(삭제)
- 데이터 삭제:
db.컬렉션명.update({다큐먼트})
UPDATE EXAMPLE )
db.users.remove({name:'nero'})
출력 **성공 시 삭제된 개수가 반환
WriteResult({ "nRemoved" : 1 })
3. Mongoose 쿼리 수행 예(전체 코드 설명)
Mongoose는 Sequelize와 다르게 ODM(Object Document Mapping)이다. 릴레이션이 아니라 다큐먼트를 사용하기 때문이다.
이전에 포스팅했던 Sequelize(with MySQL) -2의 전체 코드와 동일한 작업을 하는 코드를 작성해보겠다. 해당 포스팅에 설명이 꼼꼼하고, 코드도 매우 유사하므로 이해가 안 되면 찾아가보자!
Mongoose 환경 세팅
[npm 설치] (https://velog.io/@delay100/%ED%8C%A8%ED%82%A4%EC%A7%80-%EB%A7%A4%EB%8B%88%EC%A0%80feat.-npm#4-package-%EC%84%A4%EC%B9%98%ED%95%B4%EB%B3%B4%EA%B8%B0featnodemon) 후 아래의 모듈 설치
+ [설치할 모듈]
- express
- nodemon(개발용)
- morgan
- nunjucks
- mongoose
Git [learn-mongoose/package.json
]
{
"name": "delay100_mongoose",
"version": "1.0.0",
"description": "",
"main": "app.js",
"scripts": {
"start": "nodemon app"
},
"author": "delay100",
"license": "ISC",
"dependencies": {
"express": "^4.17.2",
"mongoose": "^6.1.8",
"morgan": "^1.10.0",
"nunjucks": "^3.2.3"
},
"devDependencies": {
"nodemon": "^2.0.15"
}
}
Git [learn-mongoose/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.envPORT || 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'), '번 포트에서 대기 중');
});
Git [learn-mongoose/views/mongoose.html
]
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>몽구스 서버</title>
<style>
table {
border: 1px solid black;
border-collapse: collapse;
}
table td,
table th {
border: 1px solid black;
}
</style>
</head>
<body>
<div>
<form id="user-form">
<fieldset>
<legend>사용자 등록</legend>
<div><input id="username" type="text" placeholder="이름"></div>
<div><input id="age" type="number" placeholder="나이"></div>
<div><input id="married" type="checkbox">
<label for="married">결혼여부</label>
</div>
<button type="submit">등록</button>
</fieldset>
</form>
</div>
<br>
<table id="user-list">
<thead>
<tr>
<th>아이디</th>
<th>이름</th>
<th>나이</th>
<th>결혼여부</th>
</tr>
</thead>
<tbody>
{% for user in users %}
<tr>
<td>{{user.id}}</td>
<td>{{user.name}}</td>
<td>{{user.age}}</td>
<td>{{'기혼' if user.married else '미혼'}}</td>
</tr>
{% endfor %}
</tbody>
</table>
<br>
<div>
<form id="comment-form">
<fieldset>
<legend>댓글 등록</legend>
<div><input id="userid" type="text" placeholder="사용자 아이디"></div>
<div><input id="comment" type="text" placeholder="댓글"></div>
<button input="input" id="submit">등록</button>
</fieldset>
</form>
</div>
<br>
<table id="comment-list">
<thead>
<tr>
<th>아이디</th>
<th>작성자</th>
<th>댓글</th>
<th>수정</th>
<th>삭제</th>
</tr>
</thead>
<tbody></tbody>
</table>
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<script src="/mongoose.js"></script>
</body>
</html>
Git [learn-mongoose/views/error.html
]
<h1>{{message}}</h1>
<h2>{{error.status}}</h2>
<pre>{{error.stack}}</pre>
Git [learn-mongoose/schemas/index.js
]
// sequelize의 테이블 만드는 것
const mongoose = require('mongoose');
const connect = () => {
// 배포 환경이 아닌 경우(ex. 개발 환경) debug를 세팅함 - 몽구스가 생성하는 쿼리를 콘솔에 출력
if (process.env.NODE_ENV !== 'production') {
mongoose.set('debug', true);
}
// 몽구스와 몽고디비 연결
mongoose.connect('mongodb://jiyeon:system@127.0.0.1:27017/admin', { // mongodb://이름:비밀번호@host:27017/admin
dbName: 'nodejs', // 접속을 시도하는 주소의 데이터베이스
useNewUrlParser: true, // 굳이 없어도 되는데 콘솔에 에러 뜨는 것 없애기1
useCreateIndex: true, // 굳이 없어도 되는데 콘솔에 에러 뜨는 것 없애기2
}, (error) => {
if (error){
console.log('몽고디비 연결 에러', error);
} else {
console.log('몽고디비 연결 성공');
}
});
};
// 이벤트 리스너
mongoose.connection.on('error', (error) => {
console.log('몽고디비 연결 에러', error); // 에러 발생 시 에러 내용을 기록하고,
});
mongoose.connection.on('disconnected', () =>{
console.log('몽고디비 연결이 끊겼습니다. 연결을 재시도합니다.'); // 연결 종료 시 재연결 시도
connect();
});
module.exports = connect;
Git [learn-mongoose/schemas/user.js
]
// sequelize의 models와 동일
const mongoose = require('mongoose');
const {Schema} = mongoose;
const userSchema = new Schema({ // 스키마를 생성
// _id를 기본 키로 생성하므로 _id 필드는 적어줄 필요가 없음
name: {
type: String, // 타입(자료형)을 String으로 지정
require: true, // 필수
unique: true, // 고유 값
},
age: {
type: Number, // 타입을 Number로 지정
require: true,
},
married: {
type: Boolean,
required: true,
},
comment: String,
createdAt: {
type: Date,
default: Date.now, // 기본 값: 데이터 생성 당시의 시간
},
});
module.exports = mongoose.model('User',userSchema);
Git [learn-mongoose/public/mongoose.js
]
const mongoose = require('mongoose');
const { Schema } = mongoose;
const { Types: { ObjectId} } = Schema;
const commentSchema = new Schema({
commenter: {
type: ObjectId,
require: true,
ref: 'User', // 스키마의 사용자 ObjectId가 들어감 - 몽구스가 JOIN과 비슷한 기능을 할 때 사용됨
},
comment: {
type: String,
required: true,
},
createdAt: {
type: Date,
default: Date.now, // 데이터 생성 당시 시간
},
});
module.exports = mongoose.model('Comment', commentSchema);
// 사용자 이름을 클릭할 때 댓글 로딩
document.querySelectorAll('#user-list tr').forEach((el)=> {
el.addEventListener('click', function() {
const id = el.querySelector('td').textContent;
getComment(id);
});
});
// 사용자 로딩
async function getUser(){
try {
const res = await axios.get('/users');
const users = res.data;
console.log(users);
const tbody = document.querySelector('#user-list tbody');
tbody.innerHTML = '';
users.map(function (user) {
const row = document.createElement('tr');
row.addEventListener('click', () => {
getComment(user._id);
});
// row 셀 추가
let td = document.createElement('td');
td.textContent = user._id;
row.appendChild(td);
td = document.createElement('td');
td.textContent = user.name;
row.appendChild(td);
td = document.createElement('td');
td.textContent = user.age;
row.appendChild(td);
td = document.createElement('td');
td.textContent = user.married ? '기혼' : '미혼';
row.appendChild(td);
tbody.appendChild(row);
});
} catch(err){
console.log(err);
}
}
// 댓글 로딩
async function getComment(id){
try {
const res = await axios.get(`/users/${id}/comments`);
const comments = res.data;
const tbody = document.querySelector('#comment-list tbody');
tbody.innerHTML = '';
comments.map(function (comment) {
// row 셀 추가
const row = document.createElement('tr');
let td = document.createElement('td');
td.textContent = comment._id;
row.appendChild(td);
td = document.createElement('td');
td.textContent = comment.commenter.name;
row.appendChild(td);
td = document.createElement('td');
td.textContent = comment.comment;
row.appendChild(td);
const edit = document.createElement('button');
edit.textContent = '수정';
edit.addEventListener('click', async () => { // 수정 클릭 시
const newComment = prompt('바꿀 내용을 입력하세요');
if(!newComment){
return alert('내용을 반드시 입력하셔야 합니다');
}
try{
await axios.patch(`/comments/${comment._id}`, { comment: newComment});
getComment(id);
} catch(err){
console.log(err);
}
});
const remove = document.createElement('button');
remove.textContent = '삭제';
remove.addEventListener('click', async() => { // 삭제 클릭 시
try {
await axios.delete(`/comments/${comment._id}`);
getComment(id);
} catch(err){
console.log(err);
}
});
// 버튼 추가
td = document.createElement('td');
td.appendChild(edit);
row.appendChild(td);
td = document.createElement('td');
td.appendChild(remove);
row.appendChild(td);
tbody.appendChild(row);
});
} catch(err){
console.log(err);
}
}
// 사용자 등록 시
document.getElementById('user-form').addEventListener('submit', async(e)=> {
e.preventDefault();
const name = e.target.username.value;
const age = e.target.age.value;
const married = e.target.married.checked;
if(!name){
return alert('이름을 입력하세요');
}
if(!age){
return alert('나이를 입력하세요');
}
try {
await axios.post('/users', { name, age, married});
getUser();
} catch(err){
console.log(err);
}
e.target.username.value = '';
e.target.age.value = '';
e.target.married.checked = false;
});
// 댓글 등록 시
document.getElementById('comment-form').addEventListener('submit', async (e) => {
e.preventDefault();
const id = e.target.userid.value;
const comment = e.target.comment.value;
if(!id){
return alert('아이디를 입력하세요');
}
if(!comment){
return alert('댓글을 입력하세요');
}
try {
await axios.post('/comments', {id, comment});
getComment(id);
} catch(err){
console.log(err);
}
e.target.userid.value='';
e.target.comment.value='';
});
Git [learn-mongoose/routes/index.js
]
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.log(err);
next(err);
}
});
module.exports = router;
Git [learn-mongoose/routes/users.js
]
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.log(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.log(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.log(err);
next(err);
}
});
module.exports = router;
Git [learn-mongoose/routes/comments.js
]
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.log(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.log(err);
next(err);
}
})
.delete(async (req, res, next)=>{
try {
const result = await Comment.remove({ _id: req.params.id});
res.json(result);
} catch(err){
console.log(err);
next(err);
}
});
module.exports = router;
--
실행 화면
** 실행 시 서버를 꼭 켜줘야 함
입력(cmd)
C:\Program Files\MongoDB\Server\5.0\bin> mongod --auth
실행화면(console)
실행화면(웹 브라우저) - http://127.0.0.1:3002
+세부적인 실행 화면은 이전 포스팅 참고
공부중에 mongoose.js
의 가장 위에있는 함수인 document.querySelectorAll('#user-list tr').forEach((el)=> {
와 getUser()
함수의 row.addEventListener('click',
을 왜 두개나 쓰는지 이해가 안 됐다. 둘 다 row를 클릭했을 때 실행되는 것이 아닌가? 그럼 중복 코드가 아닌가? 생각했다.
=> 알게된 점document.querySelectorAll('#user-list tr').forEach((el)=> {
함수는 html이 처음 로딩됐을 때 실행되고(이미 있는 데이터), row.addEventListener('click',
함수는 새로운 데이터를 추가한 경우 row 클릭 시 댓글을 로딩하기 위함이었다.
sequelize를 공부할 때는 알지 못했는데, 한 번 더 코드를 보면서 이해한걸 보니 역시 코드는 여러번 보는 게 중요한가 보다,,
그래도 sequelize와 mongoose가 비슷한 부분이 많아서 주석/설명없이 혼자 이해할 수 있는 부분이 많았다!
'Study > Node.js' 카테고리의 다른 글
15 - SNS 만들기 -2(with Node, MySQL, Nunjucks) ★ (0) | 2022.06.13 |
---|---|
14 - SNS 만들기 -1(with Node, MySQL, Nunjucks) (0) | 2022.06.13 |
12 - Sequelize(with MySQL) -2 + 전체 코드 및 설명 ★ (0) | 2022.06.13 |
11 - Sequelize(with MySQL) -1 (0) | 2022.06.13 |
10 - express 웹 서버 만들기 -3(템플릿 엔진(feat. 퍼그, 넌적스)) (0) | 2022.06.13 |