본문 바로가기

Backend/Node.js

Node.js 코딩 패턴 (Routes-Controllers-Services 구조)

 

서버 개발을 하다보면 코드의 특성별로 소스 파일을 나누게 되어있어요.

Node도 마찬가지에요.

처음에 잘 구조를 잡아두면 유지보수할 때 큰 이득을 볼 수 있어요.

구조만 잘 잡아줘도 Node에서도 충분히 복잡한 비즈니스를 말끔하게 볼 수 있습니다.

 

반대로 매우 간단한 로직을 나눠버리면 비효율적인 작업이 될 수도 있으니 잘 판단해서 디자인해야되요.

 

우선, 기본으로 사용할 수 있는 Routes-Controllers-Services 구조로 게시판 CRUD 예제를 구현해볼게요.

board-route

var express = require('express')
var router = express.Router()
const BoardController = require('../controllers/board-controller')

router.get('/:boardId', BoardController.getBoard)
router.get('/', BoardController.getBoards)
router.post('/', BoardController.insertBoard)
router.patch('/:boardId', BoardController.updateBoard)
router.delete('/:boardId', BoardController.deleteBoard)
router.get('/:boardId/comment', BoardController.getComments)
router.post('/:boardId/comment', BoardController.insertComment)
router.patch('/:boardId/comment/:commentId', BoardController.updateComment)
router.delete('/:boardId/comment/:commentId', BoardController.deleteComment)

module.exports = router

board-controller

const BoardService = require('../services/board-service')

exports.getBoard = async (req, res, next) => {
    let { boardId } = req.params
    try {
        let rows = await BoardService.getBoard(boardId)
        return res.json(rows[0])
    } catch (err) {
        return res.status(500).json(err)
    }
}

// 생략...

exports.deleteComment = async (req, res, next) => {
    let { boardId, commentId } = req.params
    try {
        let del = await BoardService.deleteComment(boardId, commentId)
        return res.json(del)
    } catch (err) {
        return res.status(500).json(err)
    }
}

board-service

const pool = require('../database/pool')
const BoardQuery = require('../queries/board-query')

exports.getBoard = async (boardId) => {
    try {
        let data = await pool.query(BoardQuery.getBoard, [boardId])
        return data[0]
    } catch (err) {
        console.log(err)
        throw Error(err)
    }
}

// 생략...

exports.deleteComment = async (boardId, commentId) => {
    let conn = await pool.getConnection()
    try {
        await conn.beginTransaction()

        let del = await conn.query(BoardQuery.deleteComment, [commentId])
        if (del[0].affectedRows == 1) {
            let upd = await conn.query(BoardQuery.minusCommentCnt, [boardId])
        }
        await conn.commit()

        return del[0]
    } catch (err) {
        conn.rollback()
        console.log(err)
        throw Error(err)
    } finally {
        conn.release()
    }
}

board-query

exports.getBoard = 'select board_id, title, content from board where board_id = ?'
exports.getBoards = 'select board_id, title from board limit ?, ?'
exports.insertBoard = 'insert into board set ?'
exports.updateBoard = 'update board set title = ?, content = ?, upd_dt = now() where board_id = ?'
exports.deleteBoard = 'delete from board where board_id = ?'
exports.getComments = 'select board_id, title, content from board where board_id = ?'
exports.insertComment = 'insert into board set ?'
exports.plusCommentCnt = 'update board set comment_cnt = comment_cnt + 1 where board_id = ?'
exports.updateComment = 'update board_comment set content = ?, upd_dt = now() where comment_id = ?'
exports.deleteComment = 'select board_id, title, content from board where board_id = ?'
exports.minusCommentCnt = 'update board set comment_cnt = comment_cnt - 1 where board_id = ?'

 

mysql2 모듈을 이용하여 쿼리를 직접 작성/호출하는 방식이여서 쿼리를 별도로 관리해야 해요. (안그럼 후회해요.)

중복 쿼리를 작성할 확률도 줄어들고 굉장히 긴 쿼리를 작성하는 경우에 이점이 있어요.

 

sql 작성이 아닌 ORM을 사용하면 Queries를 작성할 필요 없이 Models를 관리하게 되요.

자연스럽게 Models-Routes-Controllers-Services 구조로 잡으면 됩니다.

 

위와 같이 소개 드렸지만 개인적으로 JavaScript와는 맞지 않은 것 같아 전 따라가지 않는 편입니다.

JavaScript 언어 자체가 자유도나 유연성이 높아서 직접 연구해보시는 것이 더 좋은 것 같아요!