노알못 Node.js 정리: 데이터베이스(MongoDB)

Table of Content

이 글은 Node.js 교과서(조현영 저) 라는 책으로 공부한 내용을 정리한 글입니다.

NoSQL

  • 고정 테이블이 없음
    • 테이블에 상응하는 컬렉션이 존재하나 컬럼을 따로 정의하지 않음
    • 컬렉션에는 어떠한 데이터도 다 들어갈 수 있음.
  • 트랜젝션 미지원
    • 여러 쿼리가 모두 정상적으로 수행되거나 아예 하나도 수행되지 않음을 보장하지 않음
    • MongoDB 4 버전부터 트랜젝션 지원
  • 사용하는 이유: 확장성 및 가용성 때문
    • 데이터를 빠르게 삽입
    • 데이터를 여러 서버에 쉽게 분산

SQL vs NoSQL

SQL(MySQL, MariaDB) NoSQL(MongoDB)
데이터 입력 정의된 컬럼 대로 데이터 입력 자유로운 데이터 입력
JOIN 지원 미지원
트랜젝션 지원 미지원(MongoDB 4 버전부터 지원)
특징 안정성, 일관성 확장성, 가용성
용어 테이블, 로우, 컬럼 컬렉션, 도큐먼트, 필드
사용 사례 일관성을 중요시하는 DB(예약 시스템) 확장성 및 가용성을 중요시하는 DB(빅데이터, 메시징, 세션관리 등)

MongoDB 설치 및 기본 설정(MacOS)

설치

https://docs.mongodb.com/manual/tutorial/install-mongodb-on-os-x 참고

  1. brew 설치
  2. brew update 명령어로 brew 업데이트
  3. brew tap mongodb/brew 명령어 실행
   ➜ ~ brew tap mongodb/brew
   ==> Tapping mongodb/brew
   Cloning into '/usr/local/Homebrew/Library/Taps/mongodb/homebrew-brew'...
   remote: Enumerating objects: 13, done.
   remote: Counting objects: 100% (13/13), done.
   remote: Compressing objects: 100% (11/11), done.
   remote: Total 13 (delta 4), reused 4 (delta 0), pack-reused 0
   Unpacking objects: 100% (13/13), done.
   Tapped 6 formulae (45 files, 60.2KB).
  1. install mongodb-community@4.2 명령어로 설치: 당시 안정화된 최신 버전이 4.2였음.
   ➜ ~ brew install mongodb-community@4.2
   ==> Installing mongodb-community from mongodb/brew
   ==> Downloading https://fastdl.mongodb.org/osx/mongodb-macos-x86_64-4.2.2.tg
   ==> Caveats
   To have launchd start mongodb/brew/mongodb-community now and restart at login:
     brew services start mongodb/brew/mongodb-community
   Or, if you don't want/need a background service you can just run:
     mongod --config /usr/local/etc/mongod.conf
   ==> Summary
   ????  /usr/local/Cellar/mongodb-community/4.2.2: 21 files, 274.5MB, built in 10 seconds
  1. MongoDB 실행: brew services start mongodb-community@4.2
    • MongoDB 재시작: brew services restart mongodb-community@4.2
    • MongoDB 종료: brew services stop mongodb-community@4.2

참고로, 이렇게 설치된 MongoDB 관련 디렉토리 및 파일 경로는 다음과 같음

  • the configuration file: /usr/local/etc/mongod.conf
  • the log directory path: /usr/local/var/log/mongodb
  • the data directory path: /usr/local/var/mongodb

관리자 계정 추가

  1. MongoDB 프롬프트에 접속하여 관리자 계정 추가
➜  ~ mongo  # MongoDB 프롬프트 접속
MongoDB shell version v4.2.2
connecting to: mongodb://127.0.0.1:27017/?compressors=disabled&gssapiServiceName=mongodb
> use admin  # admin으로 전환
switched to db admin
> db.createUser({ user:'USER_NAME', pwd:'P@SsW0Rd!', roles: ['root'] })  # 관리자 계정 추가
Successfully added user: { "user" : "USER_NAME", "roles" : [ "root" ] }
> 
  1. Ctrl+C 키로 mongodb를 빠져나온 후 설정 파일 수정
➜  ~ vi /usr/local/etc/mongod.conf  # 설정 파일 수정

systemLog:
  destination: file
  path: /usr/local/var/log/mongodb/mongo.log
  logAppend: true
storage:
  dbPath: /usr/local/var/mongodb
net:
  bindIp: 127.0.0.1

# 아래 두 줄 추가
security:
  authorization: enabled
  1. mongodb 재시작
➜  ~ brew services restart mongodb-community@4.2  # 재시작
Stopping `mongodb-community`... (might take a while)
==> Successfully stopped `mongodb-community` (label: homebrew.mxcl.mongodb-c
==> Successfully started `mongodb-community` (label: homebrew.mxcl.mongodb-c
  1. 계정 접속
➜  ~ mongo admin -u USER_NAME -p P@SsW0Rd!

컴퍼스(Compass)

MongoDB 관리 도구. https://www.mongodb.com/products/compass 에서 다운로드

설치 후 Authentication을 Username/Password로 변경 후 위에서 생성한 계정 정보를 입력하여 접속

데이터베이스 및 컬렉션 생성

데이터베이스 생성: use DATABASE_NAME

> use nodejs
switched to db nodejs

데이터베이스 목록 확인: show dbs

> show dbs
admin   0.000GB
config  0.000GB
local   0.000GB
# 데이터가 없는 데이터베이스는 표시되지 않음

현재 사용 중인 데이터베이스 확인: db

> db
nodejs

컬렉션(테이블) 생성: db.createCollection('COLLECTION_NAME')

> db.createCollection('users')
{ "ok" : 1 }
> db.createCollection('comments')
{ "ok" : 1 }

컬렉션(테이블) 출력: show collections

> show collections
comments
users

CRUD

Create

데이터 생성: db.COLLECTION_NAME.save(DOCUMENT)

> db.users.save({ name:'pengsu', age: 10, married: false, comment:'안녕요?ㅎ', createAt: new Date() });
WriteResult({ "nInserted" : 1 })
> db.users.find({ name:'pengsu' }, { _id: `` })
{ "_id" : ObjectId("5e2aa47b8af43d69936a6588") }

> db.comments.save({ commenter: ObjectId("5e2aa47b8af43d69936a6588"), comment:'안녕요?ㅎ', createdAt: new Date() });
WriteResult({ "nInserted" : 1 })

Read

모든 데이터 조회: db.COLLECTION_NAME.find({})

특정 필드만 조회: db.COLLECTION_NAME.find({}, { FIELD: VALUE })

> db.users.find({}, { _id:0, name:1, married:1 })  # _id는 기본적으로 가져오게 돼있으므로 0 또는 false를 입력하여 가져오지 않도록 함
{ "name" : "pengsu", "married" : false }
{ "name" : "ingyeo", "married" : true }

조회 조건: $gt(초과), $gte(이상), $lt(미만), $lte(이하), $ne(같지않음), $or(또는), $in(배열요소 중 하나)

> db.users.find({ $or: [{ age:{ $gt: 10 } }, { married: false }] }, {_id: 0,
 name: 1, age: 1 })
{ "name" : "pengsu", "age" : 10 }
{ "name" : "ingyeo", "age" : 100 }

정렬: sort() 메서드 사용

> db.users.find({}, { _id:0, name:1, age:1 }).sort({ age: -1 })  # 1: 오름차순, -1: 내림차순
{ "name" : "ingyeo", "age" : 100 }
{ "name" : "pengsu", "age" : 10 }

조회할 도큐먼트 개수 설정: limit() 메서드 사용

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

몇 개 건너뛸지 설정: skip() 메서드 사용

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

UPDATE

> db.users.update({ name: 'ingyeo'}, { $set: { age: 10 } })
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })  # $set 연산자를 사용하여 업데이트

Delete

> db.users.remove({ name: 'ingyeo' })
WriteResult({ "nRemoved" : 1 })

Mongoose

자바스크립트 객체와 매핑하기 위한 ODM(Object Document Mapping)

  • MongoDB에 데이터를 넣기 전 데이터 필터링 수행
  • JOIN 기능을 populate() 메서드로 어느 정도 보완
  • ES2015 프로미스 문법 및 강력하고 가독성 높은 쿼리 빌더 지원

실습 준비

  1. express 프로젝트 생성: express learn-mongoose --view=pug
  2. npm 패키지 설치: cd learn-mongoose && npm i
  3. Mongoose 설치: npm i mongoose

MongoDB 연결

/*** schemas/index.js ***/

const mongoose = require('mongoose');

module.exports = () => {
  const connect = () => {
    // 개발 환경이 아닐 때 Mongoose가 생성하는 쿼리 내용을 콘솔로 확인
    if (process.env.NODE_ENV !== 'production') {
      mongoose.set('debug', true);
    }

    // Mongoose와 MongoDB 연결
    mongoose.connect('mongodb://USER_NAME:PASSWORD!@localhost:27017/admin', {
      dbName: 'DB_NAME', useNewUrlParser: true, useUnifiedTopology: true, // DeprecationWarning 없애기 위한 속성 2개
    }, (error) => {
      if (error) { console.log('몽고디비 연결 에러', error); } 
      else { console.log('몽고디비 연결 성공'); }
    });
  };
  connect();

  // 이벤트 리스너
  mongoose.connection.on('error', (error) => {
    console.error('몽고디비 연결 에러', error);
  });
  mongoose.connection.on('disconnected', () => {
    console.error('몽고디비 연결이 끊겼습니다. 연결을 재시도합니다.');
    connect();
  });

  // User 및 Comment 스키마 연결
  require('./user');
  require('./comment');
};
/*** app.js ***/

// ...

var indexRouter = require('./routes/index');
var usersRouter = require('./routes/users');
var connect = require('./schemas');

var app = express();
connect();

// ...

스키마 정의

주요 속성

  • type: String, Number, Date, Buffer, Boolean, Mixed, ObjectId, Array
  • required: 필수값
  • unique: 고유값
  • default: 기본값
/*** schemas/users.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,
  createdAt: {
    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', // commenter 필드에 User 스키마의 ObjectID가 들어감. 추후 JOIN 기능을 사용하기 위해.
  },
  comment: {
    type: String,
    required: true,
  },
  createdAt: {
    type: Date,
    default: Date.now,
  },
});

// model() 메서드의 첫번째 인자로 컬렉션 이름을 만듦
// 예: Comment -> Comments
// 컬렉션 이름을 직접 만들고 싶다면 세 번째 인자에 컬렉션 이름을 지정하면 됨
// 예: mongoose.model('User', userSchema, 'user_table')
module.exports = mongoose.model('Comment', commentSchema);

쿼리 실행

/*** routes/index.js ***/
var express = require('express');
var router = express.Router();

var User = require('../schemas/user');

/* GET home page. */
router.get('/', function (req, res, next) {
  // DB 내 모든 컬렉션을 JSON 형태로 응답
  User.find({})
    .then((users) => { res.json(users); })
    .catch((err) => {
      console.error(err);
      next(err);
    });
});

module.exports = router;

댓글 남기기