BackEnd/Node.js

Node.js 프로젝트 리팩토링(1)

Bubbles 2022. 2. 27. 00:02

------------------------------------------------------------ 사담 ----------------------------------------------------------------

최근에 Node.js 스터디를 했고, 인턴십 면접도 보면서 기존에 했었던 프로젝트를 리팩토링 해야겠다고 생각했다. 인턴십 사담을 좀 해보자면, 노드 쓰는곳 2군데 스프링 한군데 넣었고 노드는,, 서류는 다 붙었는데 면접 탈했다 ^_^

쨌든 기존 프로젝트를 좀 더 다듬어서 다음학기에도 또 지원해보고자 ... 리팩토링 과정을 좀 블로그에 기록해두고자 한다. 

-----------------------------------------------------------------------------------------------------------------------------------

 

1. 프로젝트 구조 : MVC 패턴을 적용해보자

$ npm install express-generator -g

위 명령어를 vscode 터미널에 치면 express의 기본적인 구조를 자동으로 형성해준다. 그 뒤에 npm install 명령어로 필요한 모듈 설치를 해주면 된다. 

출처 : https://expressjs.com/ko/starter/generator.html

그럼 위와 같은 구조가 만들어지는데, 여기서 아래처럼 바꿨다. 

 

 

내 코드의 기존 문제점이라고 해야하나? 노드의 모듈화를 잘 적용하지 못한 것 같다. 처음 노드를 공부할 때에는 스치듯 지나간 다른 사람들의 코드를 보면서 왜 찾아보기 복잡하게 저렇게 해놨지? 라는 생각을 했었다. 처음엔 조금 이해하기 어려웠지만 모듈화 시키는 쪽이 좀 더 개발하기 편리할 것 같다. 

 

✅ MVC 패턴

- Model : 데이터와 비즈니스 로직 관리
    - 어플리케이션의 정보, 데이터를 나타냄
    - 뷰나 컨트롤러에 대한 정보를 몰라야 함
- View : 클라이언트와 상호작용이 일어나는 부분, 레이아웃과 화면 처리
    - 변경이 일어날 시 모델에게 변경 내용 전달 
- Controller : 유저의 요청을 처리해서 응답하는 부분. 모델과 뷰 부분으로 라우팅
    - 모델과 뷰 사이를 이어주는 브릿지 역할
    - 모델이나 뷰의 변경을 모니터링, 변경 사항을 각 구성요소에 통지해야 함


 

2. Modules

- db와 연결하는 부분, 상태 코드, 메시지 등을 담아두는 폴더이다.

- message, status는 기존 프로젝트에서도 있었는데 달라진 부분은 connectionPool, mysql부분이다. 

- mysql.js (db정보는 각자 다 다를테니 맞춰서 작성) 

📌 database와의 연결을 생성하는 모듈

const mysql = require('mysql2/promise');
const database = {
    host: 'host 정보'
    user: 'username 정보',
    database: 'database정보',
    password: 'database비밀번호'
}
module.exports = mysql.createPool(database);

- connectionPool.js

const poolPromise = require('./mysql');
module.exports = {
    queryParam : async (sql) => {
        return new Promise (async (resolve, reject) => {
            try {
                const pool = await poolPromise; // pool생성
                const connection = await pool.getConnection();
                try {
                    const result = await connection.query(sql); //sql 쿼리 수행
                    connection.release();
                    resolve(result); //프로미스 이행
                } catch (err) {
                    connection.release();
                    reject(err); // 프로미스 실패
                }
            } catch (err) {
                reject(err);
            }
        })
    }
}

- database에 쿼리 날리는 방식을 async-await으로 구현

- database에 미리 연결된 connection을 생성해서 pool에 저장, 필요 시 pool의 connection 가져다 쓰고, 사용이 끝나면 반환(connection.release())

📌 queryParam : sql문을 매개변수로 받아서 데이터베이스로부터 쿼리 결과를 받아오는 비동기 함수

 


 

3. Models

- user.js : 사용자 관련 작업들 담아둠. 로그인, 닉네임 중복확인 등의 작업들을 정의해두었다. 

const pool = require('../modules/connectionPool');
const bcrypt = require('bcrypt');

module.exports = {
    login : async (email) => {
        const sql = `SELECT * FROM User WHERE email="${email}"`;
        try {
            const result = await pool.queryParam(sql);
            return result;
        } catch (err) {
            throw err;
        }
    },
  	/* ..... */
}

 


 

4. Controllers

- 요청에 맞춰서 models/user.js 의 작업들을 수행시킨다. 

- 예를 들어 위의 로그인 작업을 수행하는 userController 내부 코드는 아래 코드와 같다.

login : async (req, res) => {
        const {
            email, 
            pw
        } = req.body;

        if (!email || !pw) {
            return res.status(statusCode.BAD_REQUEST).send(messageCode.MISS_DATA)
        } // 필요한 데이터 누락
        
        const user = await User.login(email); //models/user.js의 작업 기다림
        if (user.length === 0) { // email 에러
            return res.status(statusCode.MATCH_ERR).send(messageCode.INVALID_USER);
        } else {
            const match = bcrypt.compare(pw, user[0][0].pw);
            if (!match) {
                return res.status(statusCode.MATCH_ERR).send(messageCode.INVALID_PW);
            }
        }

        return res.status(statusCode.SUCCESS).json({
            code: statusCode.SUCCESS,
            message: messageCode.SIGN_IN_SUCCESS,
            userIdx : user[0][0].id
        });  
    },

- 로그인이 실패했을 경우에 각각의 에러코드와 메시지를 전달한다. 성공했을 경우에는 우선 userIdx를 성공 응답과 함께 보내주는 것으로 짜둠.

- 아직 JWT 토큰을 안 달아놨는데 그건 다음편에 쓸 예정...

 

 


5. Routes

- 클라이언트로부터 들어오는 api요청을 라우팅. 적절한 controller로 보내준다. 

- 현재는 index.js, user.js 2개

- REST API구조에 맞춰 주소를 미리 정의해뒀다. userController 쪽으로 넘길거면 주소 형태가 /user/로 시작한다. 

 

📌 index.js 

const express = require('express');
const router = express.Router();

router.use('/user', require('./user'));
module.exports = router;

- /user 로 시작하는 경우 ./user(같은 routes폴더에 있는 user.js)에 넘겨준다. 

- 추후에 /post, /list 뭐 이런 라우터가 있으면 그에 맞는 router로 넘겨주게끔 위 코드 형태로 작성하면 된다.

 

📌 user.js

const express = require('express');
const router = express.Router();
const userController = require('../controllers/userController');

router.post('/login', userController.login);
router.post('/join', userController.join);
module.exports = router;

- user/login 주소로 POST 요청이 오면 userController의 login 처리

- user/join 주소로 POST 요청이 오면 userController의 join처리 

이러한 방식으로 흘러간다. 

 

 


일단 간단하게 프로젝트 구조를 좀 개선해봤다. 지금은 아직 로그인/회원가입 정도만 대강 구현해놨다. 다음 포스팅에는 다른 기능들이랑 JWT토큰 발급도 공부해서 써야겠다. 

 

🔽 참고한 블로그들