엘리스 AI트랙 4기/프로젝트

[TypeScript Express] 라우터, 컨트롤러, 미들웨어 분리

남쪽마을밤송이 2022. 6. 10. 21:47

 [01 계층 분리의 필요성 ] 

  • 사실 1, 2차 프로젝트 때는 Controller 없이 3계층 구조로 크게는 Router / Service / DB로 나뉘고, route 방식으로는 Router와 Middleware만 사용했는데 이번 프로젝트에서 같은 백엔드 팀원의 제안으로 Router와 Middleware를 나누게 되었다.
  • 일단 모든 코드를 나누지 않으면 어떤 엔드포인트로 접근했을 때 어떤 동작을 하게 되는지 한 눈에 볼 수 있어 유용할 수 있다. 그런데 조금만 기능이 복잡해지면 이런 방법으로 라우팅을 관리하는 것은 좋지 않다.
  • 예를 들어 입력값을 검증하고 사용자가 로그인 한 뒤 기능을 수행하는 로직이 모두 Router 파일에 있으면 코드의 재생산성과 유지보수성이 현저히 떨어진다.
  • 따라서 백엔드에서 정확한 계층 분리는 장기적으로 봤을 때 꼭 필요하다.

 [02 Router / Controller / Middleware의 차이

  • Router : 엔드포인트와 해당 엔드포인트에서 실행돼야 할 로직을 연결해주는 역할
  • Controller : Middleware의 일종이지만 메인 로직을 담당하므로 분리해서 관리
  • Middleware : 메인 로직의 Controller 앞뒤로 추가적인 일을 담당

 

  • 따라서 Router는 정말 코드의 전체적인 흐름, 경로만 나타내주고 Controller가 메인 로직을 담당(Service 계층으로 연결, Service는 DB계층으로 연결), Middleware는 그 외의 특별한 일을 함수로 따로 빼서 거치도록 해주는 느낌이다.

 [03 코드에 적용 ] 

 (1) userRouter.ts 

  • 이렇게 라우터 파일은 입력값 정도만 검증하고 전체적인 흐름을 파악할 수 있도록 깔끔하게 관리한다.
import { Router } from "express";
import { UserController } from "../controllers/userController";
import { check } from "express-validator";
import { validatorErrorChecker } from "../middlewares/validator";
import { loginRequired } from "../middlewares/loginRequired";

const UserRouter = Router();

// 회원가입
UserRouter.post(
  "/register",
  [
    check("user_name").exists(),
    check("email").exists().isEmail(),
    check("password").exists().isLength({ min: 8, max: 12 }),
    validatorErrorChecker,
  ],
  UserController.register,
);

// 로그인
UserRouter.post(
  "/login",
  [check("email").exists().isEmail(), check("password").exists().isLength({ min: 8, max: 12 }), validatorErrorChecker],
  UserController.login,
);

// 회원 정보 수정
UserRouter.put("/updateInfo", loginRequired, UserController.updateInfo);

// 회원 탈퇴
UserRouter.delete("/withdrawal", loginRequired, UserController.withdrawal);

export { UserRouter };

 (2) userController.ts 

  • 그리고 컨트롤러에서 입력값을 정제해서 서비스 계층의 함수에 넘겨주는 기능을 수행한다.
import { Request, Response, NextFunction } from "express";
import { UserService } from "../services/userService";
import { Users } from "../db/models/user";
import { IUserInput, IUserLoginInput, IUserInfoUpdateInput } from "../interfaces/userInput";

const UserController = {
  register: async (req: Request, res: Response, next: NextFunction) => {
    try {
      const { user_name, email, password, gender, age_range, job }: IUserInput = req.body;
      const newUser = await UserService.addUser({ user_name, email, password, gender, age_range, job });

      res.status(201).json(newUser);
    } catch (error) {
      next(error);
    }
  },
  .
  .
  .
}
  • 원래 이 코드들이 다 라우터 파일에 들어있었는데 컨트롤러 파일로 분리하는 게 좋다는 것을 이번에 알게 되었다.

출처 : https://velog.io/@hiro2474/express-router-controller-middleware