MongoDB 연결, CRUD, 검증

kangdari·2020년 2월 21일
1

MongoDB

MongoDB는 문서 지향적 NoSQL DB입니다.
새로 등록해야 할 데이터의 형식이 바뀐다고 하더라도 기존 데이터까지 수정할 필요가 없습니다.
그리고 서버의 데이터 양이 늘어나도 한 컴퓨터에서만 처리하는 것이 아니라 여러 컴퓨터로 분산하여 처리할 수 있도록 확장하기 쉽게 설계되었습니다.

컬렉션: 여러 문서가 들어 있는 곳
MongoDB는 다른 스키마를 가지고 있는 문서들이 한 컬렉션에 공존할 수 있습니다.

MongoDB 구조
서버 하나에 여러 개의 DB를 가지고 있을 수 있으며, 각 DB에는 여러개의 컬렉션이 있고, 컬렉션 내부에는 문서들이 들어있습니다.

.env 환경변수 파일 작성

몽고디비는 주소를 사용해 연결합니다. ( 호스트:포트번호/데이터베이스 이름 )

MONGO_URL = mongodb://localhost:27017/auth

mongooes로 서버와 DB 연결하기

mongoose는 MongoDB ODM 중 가장 많이 쓰이고 있습니다.
ODM(Object Document Mapping) Object: 자바스크립트 객체, Document: 몽고DB의 문서
즉, 문서를 DB에서 조회할 때 자바스크립트 객체로 바꿔 주는 역할...

models/index.js 파일에 DB와 연결하는 코드를 작성하고

import mongoose from 'mongoose';
import dotenv from 'dotenv';
dotenv.config();

const { MONGO_URL } = process.env;

// DB 연결 함수
export const connect = () =>{
    // 개발 환경이 아닐 때 몽구스가 생성하는 쿼리 내용을 콘솔에 출력
    if(process.env.NODE_ENV !== 'production'){
        mongoose.set('debug', true);
    }

    mongoose
        .connect(MONGO_URL, { useNewUrlParser: true, useFindAndModify: false })
            .then(()=> {
                console.log('Connect to MongoDB');
            })
            .catch(e => {
                console.log('Connect Error', e);
            })
        }

server.js와 연결하여 노드 실행 시 mongoose.connect도 함께 실행되도록 했습니다.

(...)
import { connect } from './models/index.js';
(...)
const app = express();
connect();

스키마 정의

스키마를 만들 때는 mongoose 모듈의 Schema를 사용하여 정의합니다.

import mongoose from 'mongoose';

const { Schema } = mongoose;
const postSchema = new Schema({
    title: String,
    body: String,
    tags: [String],
    publishedDate : {
        type: Date,
        default: Date.now,
    }
});

모델 생성

모델을 만들 때는 mongoose.model 함수를 사용합니다. ( model(스키마 이름, 스키마 객체) )
스키마 이름을 User라고 설정하면 실제 DB에 만드는 컬렉션 이름은 Users입니다.
이러한 컨벤션을 따르고 싶지않다면 model 함수의 세번째 파라미터에 원하는 이름을 작성하면 됩니다.

( ... )
// model 메서드로 스키마와 몽고디비 컬렉션을 연결하는 모듈을 만든다.
const User = mongoose.model('User', userSchema);
export default User;

스키마는 컬렉션에 들어가는 문서 내부의 각 필드가 어떤 형식으로 되어 있는지 정의하는 객체입니다. 이와 달리 모델은 스키마를 사용하여 만드는 인스턴스로, DB에서 실제 작업을 처리할 수 있는 함수들을 가지고 있는 객체입니다.

데이터 생성(create)

REST API를 기반으로 api/posts/posts.ctrl.js에 블로그 포스터를 작성하는 API인 write를 구현했습니다.

import Post from '../../models/post.js'; // Post 모델 사용 

export const write = async (req, res, next) => {
    // 클라이언트에서 보낸 요청
    const { title, body, tags } = req.body;
    const post = new Post({
        title,
        tags,
        body,
    });
    try{
        await post.save(); // DB에 저장
        res.json(post); // 클라이언트에 전달
        // res.send(post);
    }catch(e){
        res.status(500); // 서버 오류
    }
}

라우터를 설정해주고 Postman을 통해 서버가 응답한 결과를 잘 받았는지 확인했습니다.

전체 데이터 조회

데이터를 조회할 때는 모델 인스턴스의 find() 함수를 사용합니다.
find() 함수를 호출한 후에는 exec()를 붙여 주어야 서버에 쿼리를 요청합니다.

export const list = async (req, res, next) => {
    try{
        // Post 모델에서 데이터 조회
        const posts = await Post.find().exec();
        res.json(posts); // 클라이언트에 전달
    }catch(e){
        res.status(500).send(e);
    }
}

특정 데이터 조회(read)

특정 id 값을 가진 데이터를 조회할 때는 findById() 함수를 사용합니다.
id는 URL의 파라미터값으로 전달됨으로 req.params를 사용합니다.

export const read = async (req, res, next) => {
    const { id } = req.params; // url 파라미터는 req.params 사용
    try{
        const post = await Post.findById(id).exec(); // id로 검색
        if(!post){
            res.status(404).send('포스트 없음');
            return;
        }
        res.json(post); // 클라이언트에 전달
    }catch(e){
        res.status(500).send('서버 오류');
    }
}

http://localhost:4000/posts/5e501cb13e527858a8aacc0
만약 문자열을 몇 개 제거하고 요청하면 500 오류가 발생합니다. 이는 전달받은 id가 ObjectId형태가 아니어서 발생하는 서버 오류입니다.

데이터 삭제(remove)

데이터를 삭제하는 함수는 여러 개가 있습니다. 그중 findByIdAndDelete()를 사용했습니다.

export const remove = async (req, res, next) => {
    const { id } = req.params;
    try{
        await Post.findByIdAndDelete(id).exec(); // id를 찾아 삭제
        res.status(204) // Noconnect (성공했지만 응답 데이터 없음)
    }catch(e){
        res.status(500).send(e);
    }
}

데이터 수정(update)

데이터를 업데이트할 때는 findByIdAndUpdate() 함수를 사용합니다.
첫 번째 파라미터는 id, 두 번째 파라미터는 업데이트 내용, 세 번째 파라미터는 업데이트 옵션입니다.

export const update = async (req, res, next) => {
    const { id } = req.params;
    try {
        const post = await Post.findByIdAndUpdate(id, req.body, {
            new: true, // new 값이 ture이면 업데이트된 데이터를 반환.
            		   // false 이면 업데이트 되기 전 데이터를 반환
        })
        if(!post){
            res.status(404).send('포스터 없음');
        }
        res.json(post); // 클라이언트에 전달
    } catch (e) {
        res.satus(500).send(e);
    }
}

server.js에 적용할 postRouter입니다.

import express from 'express';
import * as postCtrl from '../api/posts/posts.ctrl.js';

const router = express.Router();

// http://localhost:4000/posts/
router.post('/', postCtrl.write); // 작성
router.get('/', postCtrl.list); // 전체 조회
router.get('/:id', postCtrl.read); // 특정 포스터 읽기
router.delete('/:id', postCtrl.remove); // 포스터 삭제
router.patch('/:id', postCtrl.update); // 포스터 수정

export default router;

ObjectId 검증

앞서 readAPI를 실행할 때, id가 올바른 ObjectId 형식이 아니라면 500 오류가 발생했습니다. 500 오류는 서버에서 처리하지 않아 내부적으러 문제가 생겼을 때 발생하는 것으로 지금 상황에서는 올바른 오류가 아닙니다.

잘못된 id가 전달했다면 클라이언트가 전달을 잘못 보낸것이므로 400 Bad Request 오류를 띄워주는 것이 맞습니다.

checkObjectId 미들웨어를 작성했습니다.

// api/posts/posts.ctrl.js 
import mongoose from 'mongoose';

const { ObjectId } = mongoose.Types;

// ObjectId 검증 미들웨어
export const checkObjectId = (req, res, next) =>{
    const { id } = req.params; // 클라이언트에서 보낸 아이디
    if(!ObjectId.isValid(id)){
        res.status(400).send('404 에러, 잘못된 아이디'); // Bad Reqeust
        return;
    }
    return next(); // 다음 미들웨어 호출
}

라우터에서 ObjectId 검증이 필요한 부분에 checkObjectId 미들웨어를 추가합니다.

router.get('/:id', postCtrl.checkObjectId, postCtrl.read); // 특정 포스터 읽기
router.delete('/:id', postCtrl.checkObjectId, postCtrl.remove); // 포스터 삭제
router.patch('/:id', postCtrl.checkObjectId, postCtrl.update); // 포스터 수정

Request Body 검증

wirte, update API에서 전달받은 요청 내용을 검증하는 방법을 알아보겠습니다. 포스트를 작성할 때 title, body, tags 값을 모두 전달 받아야니다. 그리고 클라이언트가 값을 빼먹을때는 400 어류가 발생해야합니다.

Joi 라이브러리를 사용하여 요청 내용 검증을 해보겠습니다.

$ yarn add joi
import Joi from 'joi';
...
export const write = async (req, res, next) => {
    // 객체가 다은 필드를 가지고 있음을 검증
    const schema = Joi.object().keys({
        title: Joi.string().required(),
        body: Joi.string().required(),
        tags: Joi.array().items(Joi.string()).required(),
    });
    const result = Joi.validate(req.body, schema);
    if(result.error){
        res.status(400).send(result.error);
        return;        
    }
...

이제 API를 호출할 때 Request Body에 필요한 필드가 빠져있다면 서버는 400 오류를 응답합니다.

update API도 위와 같이 작성하여 req.body를 검증합니다.

0개의 댓글