Node.js

[Node.js] 몽고디비

lgvv 2023. 8. 10. 22:26

몽고디비

 

이 장에서는 몽고디비를 알아둔다면 더욱 더 다양한 프로그램을 만들 수 있다.

 

몽고디비의 특징 중 하나는 자스 문법을 사용한다. 따라서 몽고디비 사용하면 자스만으로 웹 애플리케이션 만들 수 있음.

 

 

# NoSQL vs SQL

MySQL은 SQL을 사용하는 대표적인 데이터베이스다. 반면에 SQL 사용하지 않는 NoSQL(Not Only SQL)이라고 부르는 데베도 있다.

 

여러 측면에서 다르지만 몇가지만 알아보자.

차이점

 

NoSQL에는 고정된 테이블 없음. 테이블에 사응하는 컬렉션이란 개념은 있으나 칼럼을 따로 정의하지 ㅇ낳음

 

MySQL은 users 테이블을 만들 때 name, age, married 등의 칼럼과 자료형 옵션등을 정의하지만 몽고디비는 그냥 users 컬렉션을 만들고 끝. 어떠한 데이터도 들어갈 수 있음.

 

몽고디비는 JOIN없다. 그럼에도 쓰는 이유는 학장성과 가용성.

 

항공사 예약 프로그램 만든다면 예약정보는 MySQL 채팅같은 부분은 NoSQL

 

# 몽고디비설치

brew tap mongodb/brew
brew install mongodb-community
brew install mongosh

맥의 경우 /usr/local/var/mongodb

 

몽고디비 실행

brew services start mongodb-community

이제 몽고디비 프롬프트에 접속하자. 콘솔에 mongosh 명령어를 입력해보자

mongosh
test>

혹시 접속이 되지 않는다면 brew services restart mongodb-community로 서비스를 재시작한 뒤 다시 mongodb로 접속합니다.

 

지금은 누구나 접속할 수 있으므로 관리자 계정을 추가합니다.

use admin
switched to db admin
admin> db.createUser({ user: '이름', pwd: '비밀번호', roles: ['root']})
{ ok: 1 }

db.createUser로 계정을 생성할 수 있다.

roles로는 모든 권한을 사용할 수 있는 root를 부여했다.

 

몽고디비 종료하고

brew services stop mongodb-community

 

시큐리티 설정

인텔
vim /usr/local/etc/mongod.conf

실리콘
vim /opt/homebrew/etc/mongod.conf

한 후
...
sercurity:
	authorization: enabled
    
    입력후 저장(wq)

 

다시 실행start하고

monogsh admin -u 이름 -p 비밀번호

입력하면

admin> 될거임.

 

# 컴퍼스 설치

brew install --cask mongodb-compass-community

https://www.mongodb.com/try/download/compass

 

Try MongoDB Tools - Download Free Here

Free download for MongoDB tools to do more with your database. MongoDB Shell, Compass, CLI for Cloud, BI Connector and other database tools available.

www.mongodb.com

설치하면 런치패드에 나타나는데 안될때는 해당 사이트 직접 들어가서 하면 돼

 

Advanced Connection Options -> Username/Password로 바꾸고

 

Username이랑 password 빈 칸으로 둔 채 입력하면 세이브

 

다시 콘솔에서

brew services start mongodb-community
mongosh

 

데이터 베이스 만드는 명령어는

use nodejs

 

데이터베이스 목록을 확인하는 명령어는

show dbs

 

현재 사용중인 데베 확인하는 명령어는

db

데베 목록은 없지만 nodejs 데이터베이스 사용하고 있다는 것 알 수 있음.

 

컬렉션 만들려면

nodejs> db.createCollection('users')
nodejs> db.createCollection('comments')

 

# CRUD 작업하기

 

몽고디비에서 CRUD작업해보자.

 

# Create(생성)

Date나 정규표현식과 같은 자바스크립트 객체를 자료형으로 사용할 수 있음. Binary Data, OjectId, Int, Long, Demical, Timestamp, JavaScript 등 추가적인 자료형 존재. 그러나 ObjectId, Binary Data, Timestamp 외에는 잘 사용하지 않고 Undefined와 Symbol은 몽고디비에서 자료형으로 사용하지 않음.

 

ObjectId는 MySQL에서 키 값과 같다.

 

제로추가

db.users.insertOne({ name: 'zero', age: 24, married: false, comment: '안녕하세요. 몽고디비 사용 방법에 대해서 알아보자', createAt: new Date()  })

네로추가

 

 

db.users.insertOne({ name: 'nero', age: 32, married: true, comment: '안녕하세요. zero친구 nero입니다', createAt: new Date() }

 

댓글 추가

db.comments.insertOne({ commenter: ObjectId('64d477ea74ecde1716e7628e'), comment: '안녕하세요 zero입니다', createAt: new Date()})

 

# Read(조회)

 

find({});는 컬렉션 내의 모든 다큐먼트를 조회하라는 의미

db.users.find({});

// 결과
[
  {
    _id: ObjectId("64d477ea74ecde1716e7628e"),
    name: 'zero',
    age: 24,
    married: false,
    comment: '안녕하세요. 몽고디비 사용 방법에 대해서 알아보자',
    createAt: ISODate("2023-08-10T09:38:50.539Z")
  },
  {
    _id: ObjectId("64d4783974ecde1716e7628f"),
    name: 'nero',
    age: 32,
    married: true,
    comment: '안녕하세요. zero친구 nero입니다',
    createAt: ISODate("2023-08-10T09:40:09.292Z")
  }
]

 

특정 필드만 조회하고 싶다면

 

find메서드의 두번째 인수로 조회할 필드를 넣는다 1또는 true로 표시한 필드만 가져온다.

_id는 기본적으로 가져오게 되었어서 0또는 false 입력해 가져오지 않게 행햐한다.

db.users.find({}, {_id: 0, name: 1, married: 1});

[ { name: 'zero', married: false }, { name: 'nero', married: true } ]

 

조회시 조건을 좀 더 줘보자

 

$gt는 초과이란 뜻

gt 초과

gte 이상

lt 미만

lte 이하

ne 같지 않음

or 또는

in 배열 속 요소 중하나

 

 db.users.find({age: { $gt: 30 }, married: true} ,{_id: 0, name: 1, married: 1});
 
 // 결과
 [ { name: 'nero', married: true } ]

 

or을 사용하는 예시다.

 

db.users.find({ $or: [{ age: { $gt: 30 } }, { married: false }] }, { _id: 0, name: 1, age: 1 });

// 결과
[ { name: 'zero', age: 24 }, { name: 'nero', age: 32 } ]

 

이번에는 정렬

db.users.find({}, { _id: 0, name: 1, age: 1 }).sort({ age: -1 })

// 결과
[ { name: 'nero', age: 32 }, { name: 'zero', age: 24 } ]

 

개수제한

db.users.find({}, { _id: 0, name: 1, age: 1 }).sort({ age: -1 }).limit(1)

// 결과
[ { name: 'nero', age: 32 } ]

 

다큐먼트 개수 몇개 건너뛰기도 가능

db.users.find({}, { _id: 0, name: 1, age: 1 }).sort({ age: -1 }).limit(1).skip(1)

// 결과
[ { name: 'zero', age: 24 } ]

 

# Update(수정)

 

db.users.updateOne( { name: 'nero' }, { $set: { comment: '안녕하세요. 이 필드를 바꿔봅시다' } });

// 결과
{
  acknowledged: true,
  insertedId: null,
  matchedCount: 1,
  modifiedCount: 1,
  upsertedCount: 0
}

$set이라는 연산자는 어떤필드를 수정할지 정하는 연산자 이거 사용하지 않고 일반 객체를 넣는다면 다큐먼트 통쨰로 두번쨰 인수로 주어진 객체로 수정된다.

 

set

#  Delete(삭제)

db.users.deleteOne({ name: 'nero' })

삭제할 다큐먼트에 첫 번째 인수로 객체 제공하면 된다.

 

#  몽구스 사용하기

MySQL에는 시퀄라이저? 그럼 몽고디비는 몽구스

몽구스는 시퀄라이즈와 달리 ODM이라고 불림. 몽고 디비는 릴레이션이 아니라 다큐먼트를 사용하므로 ORM이 아니라 ODM임.

 

먼저 스키마라는게 생김 몽고디비는 테이블이 없어서 자유롭게 테이블 데이터를 넣을 수 있지만 자유로움이 불편함을 초래하기도 함. 

 

JOIN기능을 populate라는 메서드를 어느 정도 보완

 

package.json생성

{
  "name": "learn-sequelize",
  "version": "0.0.1",
  "description": "시퀄라이즈를 배우자",
  "main": "app.js",
  "scripts": {
    "start": "nodemon app"
  },
  "author": "ZeroCho",
  "license": "MIT",
  "dependencies": {
    "express": "^4.17.1",
    "morgan": "^1.10.0",
    "mysql2": "^2.1.0",
    "nunjucks": "^3.2.2",
    "sequelize": "^6.3.4",
    "sequelize-cli": "^6.2.0"
  },
  "devDependencies": {
    "nodemon": "^2.0.4"
  }
}
npm i express morgan nunjucks mongoose
npm i -D nodemon

몽구스에 필요한 패키지 설치

 

몽고디비 연결하기

몽고디비는 주소를 사용해 연결

mongodb://[username:password@]host[:port][/[database][?options]]

mongodb://이름:비밀번호@localhost:27017/admin

이 형태와 같다. [ ] 부분은 있어도 되고 없어도 그만!

 

schemas 폴더를 만든 다음에 index.js파일을 생성

const mongoose = require('mongoose');

const connect = () => {

	if (process.env.NODE_ENV != 'production') { 
        mongoose.set('debug', true);
    }
    
    mongoose.connect('mongodb://localhost:27017'), { // ✅ 이거 개중요
        dbName: 'nodejs',
        useNewUrlParser: true,
    }, (error) => { 
        if error { 
           console.log('몽고디비 연결', error);
        } else { 
           console.log('몽고디비 연결 성공');
        }
    });
};

mongoose.connection.on('error', (error) => { 
	console.error('몽고디비 연결 에러', error);
});

mongoose.connection.on('error', (error) => { 
	console.error('몽고디비 연결이 끊겼습ㄴ디ㅏ. 연결을 재시도 합니다.', error);
});

module.exports = connect;

개발 환경일 때만 콘솔을 통해 몽구스가 생성하는 쿼리 내용을 확인할 수 있게 하는 코드

 

 

 

 

스키마를 정의해보자 

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 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);

컬렉션 이름 바꾸기

mongoose.model('User', userSchema, 'user_table');

User일 경우 user로 강제로 생성되는데 위처럼 바꾸기 가능

 

 

# 쿼리 수행하기

// 사용자 이름 눌렀을 때 댓글 로딩
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);
      });
      // 로우 셀 추가
      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.error(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) {
      // 로우 셀 추가
      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.error(err);
        }
      });
      const remove = document.createElement('button');
      remove.textContent = '삭제';
      remove.addEventListener('click', async () => { // 삭제 클릭 시
        try {
          await axios.delete(`/comments/${comment._id}`);
          getComment(id);
        } catch (err) {
          console.error(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.error(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.error(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.error(err);
  }
  e.target.userid.value = '';
  e.target.comment.value = '';
});

 

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.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;

댓글에 관련된 CRUD 작업을 하는 라우터입니다.

 

 

ㅋㅋ ㅎㅎ ㅋㅋ

'Node.js' 카테고리의 다른 글

[Node.js] #7 MySQL  (0) 2023.07.23
[Node.js] #6 익스프레스 웹 서버 만들기  (0) 2023.07.23
[Node.js] #5 패키지 매니저  (0) 2023.07.23
[Node.js] #4 http 모듈로 서버 만들기  (0) 2023.07.22
[Node.js] #3 노드 기능 알아보기  (0) 2023.07.22