08. NodeJS & MongoDB

Kim Sang Yeob·2023년 1월 25일
0

NodeJS Book 3th

목록 보기
8/9
post-thumbnail

8.1 NoSQL & SQL

1) NoSQL

  • MySQL같은 SQL 데이터베이스와는 다른 유형의 데이터
    • NoSQL의 대표주자인 mongoDB 사용
    • JOIN : 관계가 있는 테이블끼리 데이터를 합치는 기능
    • 빅데이터, 메시징, 세션 관리 등(비정형 데이터)에는 몽고디비 사용하면 좋음
SQL(MySQL)NoSQL(mongoDB)
규칙에 맞는 데이터 입력자유로운 데이터 입력
테이블 간 JOIN 지원컬렉션 간 JOIN 미지원
안정성, 일관성확장성, 가용성
용어(테이블, 로우, 컬럼)용어(컬렉션, 다큐먼트, 필드)

8.2 몽고디비, 컴파스 설치하기

8.3 데이터베이스, 컬렉션 생성하기

1) 데이터베이스 생성하기

  • use 데이터베이스명으로 생성
admin> use nodejs;
switched to db nodejs
  • show dbs로 목록 확인
nodejs> show dbs;
admin   132.00 KiB
config   60.00 KiB
local    72.00 KiB
  • db로 현재 사용중인 데이터베이스 확인
nodejs> db
nodejs

2) 컬렉션 생성하기

  • 따로 생성할 필요 없음
    • 다큐먼트를 넣는 순간 컬렉션도 자동 생성됨
    • 직접 생성하는 명령어도 있음
    nodejs> db.createCollection('users')
    { ok: 1 }
    nodejs> db.createCollection('comments')
    { ok: 1 }
    • show collections로 현재 컬렉션 확인
    nodejs> show collections
    comments
    users

8.4 CRUD 작업하기

1) Create

  • insertOne을 통한 생성
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")
}
  • commenter을 통해 댓글 구현
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 } ]
  • 첫 번째 인수로 조회 조건 입력 가능
    • $gt나 $or같은 조건 연산자 사용
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 } ]
  • skip 메서드로 건너뛸 다큐먼트 개수 제공
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) 앱과 연결하기

  • 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/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는 고유 여부
// schemas/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,
  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',
  },
  comment: {
    type: String,
    required: true,
  },
  createdAt: {
    type: Date,
    default: Date.now,
  },
});

module.exports = mongoose.model('Comment', commentSchema);

5) 라우터 작성하기

  • 프론트엔드 코드는 서버에 요청을 보내는 AJAX 요청 위주로
  • 서버 코드는 응답을 보내는 라우터 위주로 살펴보기
// 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.error(err);
    next(err);
  }
});

module.exports = router;

6) 사용자 라우터

  • router.get, post, put, patch, delete 라우터 작성
// 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.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 라우터 작성
// 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.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) 라우터 연결하기

  • app.js에 연결
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에 접속
    • 콘솔에 찍히는 몽고디비 쿼리 확인
profile
Studying NodeJS...

0개의 댓글