-
express의 Request 타입 확장하기분석과탐구 2023. 3. 13. 11:57
express의 Request 타입 확장이 필요한 상황
// 익스프레스의 미들웨어 function auth(req, res, next) { // 인증을 다 한후에 id를 얻어낸다. // 이 id를 컨트롤러에서도 쓰고싶다. req.id = id; } app.get('/', auth, (req, res) => { // 미들웨어에서 req.id에 id를 등록하여 가져올 수 있다. const userId = req.id; });
익스프레스에 미들웨어를 적용해서 사용하다 보면, 미들웨어에서 얻은 데이터를 컨트롤러로 그대로 넘기고 싶은 경우가 있다. 해당 데이터를 컨트롤러 쪽에서 다시 얻으려고 하면 미들웨어에서 했던 일을 반복해야 하는 경우가 있기 때문이다. 자바스크립트에서라면 `req.id = id;`처럼 그대로 넘겨도 무방하나, 타입스크립트에서는 자바스크립트처럼 바로 프로퍼티를 추가할 수 없다. 이를 해결할 방법이 바로 타입을 확장하는 것이다. 기존에 있는 타입에 새로운 타입을 추가하는 등의 일을 하는 것이다.
방법1 declaration merging
첫 번째 해결 방법은 전역적으로 Request의 타입을 확장하는 것이다. 사용된 타입스크립트 기법은 declaration merging이다. 네임스페이스를 합치고, 네임스페이스 안에 있는 인터페이스도 합친다. 이름이 같은 두 인터페이스를 하나로 합치면 두 인터페이스의 프로퍼티를 모두 포함하면서 이름은 같은 인터페이스가 된다.
아래 해결법은 `in typescript, express Reqeust type extends...`정도로 검색을 하다 보면 어렵지 않게 구할 수 있는 해결법이다. 그런데, 굳이 왜 기록을 남기냐면, 타입스크립트, 익스프레스 버전에 따라 적용이 안된다는 기록을 많이 봐서 그렇다. 특히, 아래 기재한 내용 중에 `"typeRoots": ["./src/types", "./node_modules/@types"],`가 중요하다. 배열 안의 순서가 바뀌면 에러가 발생한다(왜????!).
/* 파일: src/types/express/index.d.ts */ declare namespace Express { export interface Request { id: string; } } /* 파일: src/middleware/auth.ts */ const auth = (req: Request, res: Response, next: NextFunction) => { req.id = "myid"; return next(); } /* 파일: src/routes/users/auth.ts */ const authRouter = Router(); authRouter.get('/auth', auth, (req, res) => { console.log(req.id); }); /* 파일: tsconfig.json */ "typeRoots": ["./src/types", "./node_modules/@types"], /* 버전 */ "@types/express": "^4.17.17", "express": "^4.18.2", "typescript": "^4.9.5",
방법2 인터페이스 상속
두 번째 방법은 인터페이스 상속을 이용한 해결방법이다. 이 방법은 첫 번째 방법보다는 선호도가 낮은 것 같다. 하지만, 역시 어렵지 않게 찾을 수 있다. 그렇다면 왜 기록을 남기느냐? 여기서도 그냥 기록들을 따라가면 문제가 발생하기 때문이다. 어디서 문제가 발생할까? 바로 코드를 보기 전에 상속 관계에 있는 인터페이스를 다루는 코드를 보자.
interface Base { id: string; } interface Derived extends Base { email: string; } let base: Base = {id: "myid"}; let derived: Derived = {id: "mem", email: "abcd"}; derived = base; // 에러 발생 base = derived; // 컴파일 성공
두 인터페이스를 따른 변수가 있다고 했을 때, 부모 인터페이스를 따르는 변수에는 자식 인터페이스를 따르는 변수를 대입할 수 있지만, 그 반대는 안된다. 자식 쪽에는 부모에 없는 프로퍼티가 있을 수 있기 때문이다. 하지만, 자식 프로퍼티가 옵셔널 하다면, 자식에 부모를 대입할 수 있다.
interface Base { id: string; } interface Derived extends Base { email?: string; } let base: Base = {id: "myid"}; let derived: Derived = {id: "mem", email: "abcd"}; derived = base; // 통과 base = derived; // 통과
익스프레스의 Request를 상속하여 사용하는 경우도 동일하게 발생한다. 콜백으로 등록한 함수는 익스프레스 내부에서 다시 호출을 할 텐데, Request를 상속하여 만든 인터페이스로 정의한 콜백함수를 넘기면 자식(Request를 상속한 인터페이스)에 부모(Request)를 대입한 꼴이 되기 때문이다. 따라서 Request를 상속하여 사용하려면, 프로퍼티를 옵셔널로 지정해줘야 한다.
/* 파일: src/index.d.ts */ import {Request} from "express"; export interface IAuthRequest extends Request { id?: string; } /* 파일: src/middleware/auth.ts */ const auth = (req: IAuthRequest, res: Response, next: NextFunction) => { //인증... req.id = "myid"; } /* 파일: src/routes/users/auth.ts */ const authRouter = Router(); authRouter.get('/auth', auth, (req: IAuthRequest, res) => { console.log(req.id); }
결론
declaration merging은 타입이 적혀있는 원본 코드는 수정하지 않고 내 프로젝트에서 타입을 수정한다.
인터페이스 확장은 원본 코드를 수정하지도 않고 타입도 수정하지 않는다. 일반적인 상속 관계를 이용하여 새로운 타입을 만들어낸다.
'분석과탐구' 카테고리의 다른 글
divide and conquer vs dynamic programming (0) 2023.04.08 CRA의 npm run start는 서버를 띄우는 걸까? (0) 2023.03.28 2. express 타입 이해...2 (0) 2023.01.07 1. express 타입 이해...1 (0) 2023.01.07 std::move에 대하여 (0) 2022.10.03
express의 Request 타입 확장이 필요한 상황
방법1 declaration merging
방법2 인터페이스 상속
결론