카테고리 없음
Express로 나만의 Controller 만들기
코딩루이지
2023. 9. 23. 15:48
Intro
-- 개구리를 해부하지 말고, 개구리를 만들어라 --
But middleware, by its nature, is dumb. It doesn't know which handler will be executed after calling the next() function.
nestJS-Guards
nestJS에서도 express의 middleware는 멍청하다고 한다.
내가 나만의 router의 wrapper class인 Controller을 정의해보자.
개선하고싶은점
- middleware의 구분
express의 middleware은 router랑 똑같이 생겨도 next 함수가 있으면 middleware로 동작한다.
# Using middleware
이를 명확하게 구분하자. - 경로 고정
그냥 라우터에는 첫 인자로 경로를 줄 경우 세부 경로가 지정된다. 하지만, 조금 더 명시적으로 하고싶었다. - middleware의 명시적인 동작 순서
middleware의 정의 순서는 예민해서 동작 순서에 맞게 잘 정의해야한다.
middleware들의 정의들이 여기저기 발생하며 nestJS-Guards 문서처럼 next() 함수 이후 어떤게 실행될지 예측하기 어렵다.nestJS의 guard는 데코레이터를 활용해 선언적으로 middleware들을 정의했다.
이처럼 선언적으로 이를 알아볼 수 있도록하자.
@Controller('cats')
@UseGuards(RolesGuard)
export class CatsController {}
개선하기
- 구조
개선하고 싶다고 느끼고 부족하다고 느낀점은, 내가 그런부분들이 해결될 수 있도록 강제해야 겠다고 생각이 들었다.
따라서, 상위 추상 class 를 만들거다. - middleware 구분하기
middleware를 구분하기 위해서는 next 함수를 인자로 받고 호출하는지 여부이다.
middleware 가 아닌 일반 라우터일 경우 next를 인자로 받을 수 없도록 타입을 지정해주자.
export type RouterFunction = (req: Request, res: Response) => void;
export type MiddlewareFunction = (req: Request, res: Response, next: NextFunction) => void;
- middleware 경로 명시적으로 동작 순서 나타타내기
middleware의 동작 순서는 다음과 같다.
예를들어 127.0.0.1/payment/history 에 get 요청을 보내면
- / baseMiddlewares
- /payment/history baseMiddlewares
- /payment/history getMiddlewares
- /payment/history get
순서로 함수들이 동작한다.
아래와 같이 코드가 작성되어있으면, log에 이런 메세지가 남는다.
baseMiddlewares 1
baseMiddlewares 2
history
baseMiddleware 1
history
baseMiddleware 2
history
getMiddleware 1
history
getMiddleware 2
history
get
controllers/indexController.ts
class IndexController extends Controller {
constructor(baseURL: string) {
super(baseURL);
this.baseMiddlewares = [];
this.baseMiddlewares.push((req, res, next) => {
console.log(baseURL);
console.log(`baseMiddlewares 1`);
next();
});
this.baseMiddlewares.push((req, res, next) => {
console.log(baseURL);
console.log(`baseMiddlewares 2`);
next();
});
...
controllers/payment/historyController.ts
class HistoryController extends Controller {
constructor(baseURL: string) {
super(baseURL);
this.baseMiddlewares = [];
this.baseMiddlewares.push((req, res, next) => {
console.log(baseURL);
console.log('baseMiddleware 1');
next();
})
this.baseMiddlewares.push((req, res, next) => {
console.log(baseURL);
console.log('baseMiddleware 2');
next();
})
this.getMiddlewares = [];
this.getMiddlewares.push((req, res, next) => {
console.log(baseURL);
console.log('getMiddleware 1');
next();
})
this.getMiddlewares.push((req, res, next) => {
console.log(baseURL);
console.log('getMiddleware 2');
next();
})
this.get = async (req: Request, res: Response) => {
console.log(baseURL);
console.log('get');
...
- 경로 고정하기
express만 사용하면 경로 지정이 자유롭다. 각 controller가 하나의 경로만 담당하게 고정하고싶었다. 따라서, 경로 지정은 건들 수 없도록 하였다.
Controller.ts
export default abstract class Controller {
private readonly baseURL: string
constructor(baseURL: string) {
this.baseURL = '/' + baseURL;
}
...
private로 하여 상속받은 하위 class에서 접근하지 못하도록하고, readonly 옵션으로 수정이 불가능하게 하였다. 오로지 생성자 주입만 가능하도록 하였다.
- 사용 방법
생성자에서 원하는 동작들을 등록하고, toRouter로 export 해주면 된다.
const historyController: HistoryController = new HistoryController('history');
export default historyController.toRouter();
이렇게 하면 express처럼 import하여 router로 활용할 수 있다.
import historyRouter from "./historyController.js";