Back/Node.js

[Node.js] Express-typescript 전환

8개 서버에서

 

2개는 js, 6개는 ts를 사용했는데, 유지보수가 하기가 굉장히 불편하다.

 

그래서 시간이 좀 남을 떄 조금씩 바꾸기 시작했다.

 

천천히.. 다치지않게 소중히..

 

구조도 바꿨는데, 바꾸면서 느끼는게 굉장히 많았다.

 

간단하게나마 기록을 해보려한다.

 

battle라우터를 예로 들면 현재 구조는 이렇다.

 

 

router.ts

const router: Router = Router();

router.post(
  '/',
  memberAuthMiddleWare,
  createBattleContractInterceptor,
  createBattleContractController,
  responseSuccessInterceptor,
);

일반적인 라우터.

 

들어오면,

멤버검증하는 미들웨어 -> 타입 및 예외처리만 하는 인터셉터 -> 컨트롤러 -> 서비스 -> DB -> 리스폰스 인터셉터

 

바꿨다.

 

interceptor.ts

const getBattleContractInfoInterceptor: RequestHandler = async (req, res, next) => {
  const { battleInviteCode } = req.params;
  const battleInviteCodeValidatorDto = new InviteCodeValidator();
  battleInviteCodeValidatorDto.battleInviteCode = battleInviteCode;

  await validateOrReject(battleInviteCodeValidatorDto).catch((errors) => {
    console.log('Caught promise rejection (validation failed). Errors: ', errors);
    res.locals.errorContent = JSON.stringify(errors);
    return errorGlobalFilter(req, res, next);
  });

  if (!res.locals.errorContent) next();
};

class-validator를 활용해 dto의 역할을 하는 class를 만들어둔 뒤,

들어오는 모든 데이터들을 검증하고, 이상하면 errorGlobalFilter로 빠꾸먹인다.

 

만약 에러가 없다면 컨트롤러로 넘긴다.

 

해당 인터셉터는 타입과 프론트가 보내는 데이터만 검증하지만

몇몇 컨트롤러들은 데이터 검증만하는 인터셉터말고,

특별하게 로직을 검증해야해서, 해당 인터셉터에서 로직만 검증하는 인터셉터로 next로 넘겼다.

controller.ts

const createBattleContractController: RequestHandler = async (req, res, next) => {
  try {
    const memberInfo: MemberInfo = res.locals.member;
    const battleInviteCode = await createBattleContractService.createInviteCode();
    await createBattleContractService.createBattleContract(req.body, battleInviteCode, memberInfo.member_idx);
    res.locals.responseResult = { battleInviteCode };
    return next();
  } catch (err) {
    res.locals.errorContent = err;
    return errorGlobalFilter(req, res, next);
  }
};

컨트롤러에선 로직을 처리하지않는다. 무조건 데이터를 받아오고 그 결과에 대해서만 반환하거나 에러를 낸다.

 

service.ts

const createBattleContractService = {
  createInviteCode: async () => {
    let battleInviteCode: string = await randomString();
    const battleInviteCodeFromDatabase = await selectInviteCodeFromDatabase(battleInviteCode);
    if (battleInviteCodeFromDatabase.length > 0) battleInviteCode = await createBattleContractService.createInviteCode();
    return battleInviteCode;
  }
}

서비스는 각종 로직을 처리하되,

 

디비에서 뭔가 뽑아오거나 작업해야할 때 서비스에서 디비랑 직접 연결하지않고

 

디비의 데이터와 관련된 것들은 database파일에서 가져와 활용한다.

 

database.ts

const updateSignBattleContractToDatabase = async (
  battleInviteCode: string,
  weightStart: number,
  weightGoal: number,
  memberIdx: number,
) => {
  await Promise.all([
    mysqlQueryModule
      .update('inoutBattleContent')
      .set({
        weightStart,
        weightGoal,
      })
      .whereAnd({
        battleInviteCode,
        memberIdx,
      }),
    mysqlQueryModule
      .update('inoutBattleMain')
      .set({
        battleStatus: 1,
      })
      .whereAnd({
        battleInviteCode,
      }),
  ]);
};

디비에서 작업하고 서비스로 넘길 때 활용하는 곳이다.

ORM을 따로 쓰지 않았고, 예전엔 항상 쿼리를 만들어서 썼는데, 그것도 이젠 귀찮아서..

그냥 내가 혼자 쓸 ORM(?)을 만들었다..

 

카멜-스네이크 알아서 변환해주고, 쿼리만 스트링으로 만드는 용도다..

 

이것저것 바꾸면서 느꼈는데,

 

예전에 라우터와 관련된 것들을 역할에 맡게 분리했다고 생각했지만,

 

더 분리를 할 수 있다는 것도 생각하게 됐다.

 

펑셔널하게 짠다는 장점이 뭘까 고민하게 됐다.

근데 실제 작업할 때는 오히려 너무 분리하는 것도 불편한 점도 있어서, 그 비율을 잘 맞추는게 좋겠다.