본문 바로가기
일지/FlowFroge(GTDList)

FlowForge(GTDList) 02-06 일지 (backend - express의 next)

by 리나그(ReenAG) 2024. 2. 6.
728x90
반응형

 정말 오래간만에 일지를 쓰는 것 같다. 역시 사람 마음가짐과 행동은 100% 일치할 수는 없나 보다. 여태까지의 성과, 그리고 이해한 것을 조금이라도 정리하고자 한다.

 이전에도 이야기했다시피, 난 한 가지 레퍼런스 프로젝트를 기반으로 백엔드를 만들고자 하였다. 특히, 유저 파트는 그럴 수 밖에는 없었다. Restful 한 API를 넘어서서 cookie, jsonwebtoken을 이용해서 seemless 한 유저 경험을 만들어내는 것은 적어도 그 당시 나의 능력 밖이었다. 분하지만 어쩔 수 없다. 모르는 건 배우는 수밖에. 그래서 일단 그 코드를 분석하는데도 의외로 삽질이 꽤 많이 필요했다. (지금 글을 쓰면서 생각하는 것이지만... 어쩌면 코드를 분석하는 방법이 잘못된 것은 아닐까? commit log를 참조했어야 했을지도 모르겠다.) 나는 그 코드를 이해하고 다시 이식해야 하는 상황이었는데... 일단 해야 했던 작업은 이렇다.

 

1. koa에서 express로 벡엔드 엔진을 옮긴다.

- 그러면 cookie나 router를 이용하는 방법이 달라지므로 같은 방식으로 동작하게 바꾸어야 한다.

2. user controller와 auth api는 다른 것이고, 다른 계층임을 이해한다.

- 이걸 혼동해서 1~2일 손해 보았다.

3. middleware를 이용해서 request와 response를 다루는 것을 이해하고, next의 존재의의를 알아낸다.

- express, koa를 이용한 앱의 전체적인 흐름을 깨닫기 위한 필수 지식이다. 다른 backend도 비슷하게 설계되지 않았을까?

 

적다 보니 의외로 지식적인 부분이 많았지만 삽질하면서 알아낸 거니까 적어두려고 한다. 

1. koa에서 express로 백엔드 엔진을 옮긴다.

 이건 내가 이식하는 파트 중에서 쉬운 부분에 속했다. 기본적으로 koa도 express를 더 경량화한 버전이라고도 많이 알려져 있다고 개발하는 사람들 중 1명이 가르쳐주었다. 실제로 심장이라 부를 만한 그 특유의 메카지즘은 같았다. 이는 3번 항목에 자세히 서술하겠다. 여기서는 그것 말고, 실제로 달라진 자잘 자잘한 부분들, 특히 cookie 핸들링과 router 설계에 대해서 이야기하려고 한다.

1-1. cookie

koa에는 cookie를 다루는 기능이 내장되어 있지만, express는 그렇지 못하므로, cookie-parser를 이용해야 한다. express에서 cookie를 읽고 쓰는 방법은 chatgpt도 쉽게 알려 준다.

cookie에 따로 설정할 수 있는 속성은 다음과 같은 것들도 있다.

 httpOnly나 secure 같은 옵션으로 통신하면 기본적인 보안은 확보했다고 생각하면 좋을 것 같다. 물론 아직 HTTPS를 적용 못하고 있다만, 이도 시간을 들이면 해결될 것이다. 

1-2. router

 이건 반대로 express에서는 자체 기능을 제공한다. 이렇게 적용하면 파일 구조로와 동일하게 import 하는 것이 가능하다.

 

auth/index.js

import express from 'express';
import authController from './auth.controller.js';

const authRouter = express.Router();

authRouter.post('/register', authController.register);
authRouter.post('/login', authController.login);
authRouter.get('/check', authController.check);
authRouter.post('/logout', authController.logout);

export default authRouter;

 

app.js

   import authRouter from './api/auth/index.js';
   ...
   app.use('/user', authRouter);
   ...

 

app에서 경로를 신경 쓸 필요 없이 router에서 정의되므로 상대적으로 import 하기도, 테스트를 위해서 import를 해제하기도 좋다.

2. user controller와 auth api는 다른 개념임을 이해한다.

 이건 내가 너무 CRUD를 많이 생각하다 보니 생긴 해프닝이다. 어떤 모델에 대해서 CRUD를 전부 구현할 필요는 없다. 업데이트나 리드는 구현하지 않아도 되는 경우도 허다하고... 모델 -> 해당하는 컨트롤러를 무조건 구현하는 것은 아니라는 것 정도이다. 

3. middleware를 이용한 request/response의 조작과 next의 사용법

 기본적으로 express는 자신에게 오는 request를 받고, response를 내보내는 프로그램이다. 그런데 "왜 req, res는 똑같이 argument로 받을까? 하나는 return 해서 돌려주면 될 텐데?"라는 생각을 한 적이 있었다.

코드를 질문을 옮기면 이렇다 : 

async function someEndPoint(req, res) {
	//why this?
}

async function someEndPoint(req) {
	return res;
    //and not this?
}

 그 이유는 간단히, req, res는 한 번만 처리되는 것이 아니기 때문이다. req, res가 바로 내가 설정한 엔드포인트 함수로 오는 것이 아니라 app.use()를 이용해서 넣은 middleware에 의해서 몇 차례 처리를 거친 다음 res가 결정되기 때문에, 함숫값으로 넘어오는 req, res에는 이전 middleware가 준 변화가 누적되어 있는 것이다.

 그뿐만 아니라 엔드포인트를 지정할 때 : 

app.post('/someEndPoint', preProcess, someEndPoint, afterProcess, ...);

와 같은 식으로 여러 개의 처리 함수를 지정할 수 있기 때문에, 그렇게 설계한 것이다.  (다만, 먼저 배치한 순서대로 실행한다.)

 

이런 배경 지식을 깔고 가면 next() 함수가 무슨 역할을 하는 지를 알 수 있다.

async function preProcess(req, res, next) {
	// do something about req and res...
    next();
}

async function someEndPoint(req, res) {
	// 만약 next()를 부르지 않는다면 이 함수는 실행되지 않음
	// do something about req and res...
}

 next는 req, res와 같이 함숫값으로 받을 수 있고, 이를 실행하면 되면 다음 처리함수로 req, res를 넘기고, 그 함수를 실행한다. 이는 middleware를 본인이 만들었을 때 아주 중요한 차이점을 가져오는데, 만약 middleware를 본인이 만들어두고 정작 next를 부르지 않는다면 그 이후에 동작하여야 할 모든 처리함수들이 동작하지 않기 때문이다. 그럼 서버에 req를 날려도 묵묵부답인 상황이 오니까 주의해야 한다.

 

아무튼 이런 간단한 express에 대한 이론을 적어보았다. koa도 비슷하게 설계되어서 이런 코어 메커니즘은 비슷한 것 같다. 다만 req + res = ctx라는 변수를 만들어서 사용할 뿐이다 싶다.

 

728x90
반응형