Express Request 요청 시 동적 파라미터를 어떻게 인식 할 수 있을까?

안지환·2023년 12월 6일
0

JavaScript

목록 보기
3/6

Overview

Node 라이브러리 중 Express를 사용해서 HTTP 요청을 손쉽게 사용할 수 있습니다.
URL 주소창 위에 https://localhost:3000/products/1 서버에게 요청 값이 1을 보냅니다.

주소창 위에 요청 파라미터를 Express는 어떻게 이 숫자를 요청 값을 인식할 수 있을까요?

Express 서버 실행하기

Express 서버가 실행되는 코드는 다음과 같습니다.

import express from 'express'
const app = express();

app.get('/products/:n', (req, res) => {

  res.send({
    num: request.params.n
  })
});


app.listen(3000, () => {
  console.log('Server running...')
})

URL 주소에 products/1을 보내면 Express 내부 코드에 get 메서드에서 req 파라미터에 1을 보내서 res 파라미터로 응답을 받습니다.

이 구조는 Express 요청/응답 단순한 코드로 구성이 되어 있습니다.

get 내부 구조에 대해서 자세히 살펴보겠습니다.
indes.d.ts 파일에 get은 Application Interface 파일에서 IRouterMatcher<this>로 구현 되어 있습니다.

// interface
get: ((name: string) => any) & IRouterMatcher<this>;

IRouterMatcher<this>는 this 즉 Application을 제네릭 파라미터로 사용하고 있습니다.

IRouterMatcher<this> 인터페이스는 내부 구조는 다음과 같이 되어 있습니다.

export interface IRouterMatcher<
    T,
    Method extends 'all' | 'get' | 'post' | 'put' | 'delete' | 'patch' | 'options' | 'head' = any
> {
    <
        Route extends string,
        P = RouteParameters<Route>,
        ResBody = any,
        ReqBody = any,
        ReqQuery = ParsedQs,
        LocalsObj extends Record<string, any> = Record<string, any>
    >(
        // (it's used as the default type parameter for P)
        // eslint-disable-next-line @definitelytyped/no-unnecessary-generics
        path: Route,
        // (This generic is meant to be passed explicitly.)
        // eslint-disable-next-line @definitelytyped/no-unnecessary-generics
        ...handlers: Array<RequestHandler<P, ResBody, ReqBody, ReqQuery, LocalsObj>>
    ): T;

IRouterMatcher<T, Method>로 구현이 되어 있습니다. app.get('/products/:n', (req, res)의 app.get을 사용 할 수 있는 이유 입니다.

이제 ('/products/:n', (req, res)는 IRouterMatcher에 구현 된 path:Route 와 ...handlers에 해당이 되는 것입니다.

export type RouteParameters<Route extends string> = string extends Route
    ? ParamsDictionary
    : Route extends `${string}(${string}`
        ? ParamsDictionary //TODO: handling for regex parameters
        : Route extends `${string}:${infer Rest}`
            ? (
            GetRouteParameter<Rest> extends never
                ? ParamsDictionary
                : GetRouteParameter<Rest> extends `${infer ParamName}?`
                    ? { [P in ParamName]?: string }
                    : { [P in GetRouteParameter<Rest>]: string }
            ) &
            (Rest extends `${GetRouteParameter<Rest>}${infer Next}`
                ? RouteParameters<Next> : unknown)
            : {};
  1. RouteParameters 타입 정의:

    • Route라는 제네릭 타입을 받아들입니다. 이 타입은 문자열로 제한됩니다.
  2. 타입 조건문:

    • 첫 번째 조건문: string extends Route - 만약 Route가 문자열의 확장이라면, ParamsDictionary 타입을 반환합니다. 이는 기본적인 파라미터가 없는 경우를 다룹니다.

    • 두 번째 조건문: Route가 ${string}(${string} 형태로 시작한다면, 아직 처리되지 않은 정규 표현식 파라미터를 다루기 위해 ParamsDictionary를 반환합니다. TODO 주석이 달려있어서 아직 구현되지 않은 상태입니다.

    • 세 번째 조건문: Route${string}:${infer Rest} 형태로 시작한다면, 나머지 경로의 파라미터를 처리합니다.

  3. 파라미터 추출:

    • GetRouteParameter<Rest>를 사용하여 나머지 경로에서 파라미터를 추출합니다. 이때 GetRouteParameter는 나머지 경로에서 다음 파라미터를 추출하는 역할을 합니다.

    • 추출한 파라미터에 따라 다양한 경우를 다루는데,

    • GetRouteParameter<Rest> extends never: 파라미터가 없는 경우에는 ParamsDictionary를 반환합니다.

    • GetRouteParameter<Rest> extends ${infer ParamName}?: 파라미터가 옵션인 경우에는 해당 파라미터를 포함한 객체를 반환합니다.

    • 그 외의 경우에는 해당 파라미터를 필수로 간주하고, 해당 파라미터를 포함한 객체를 반환합니다.

  4. 재귀적 호출:
    - 나머지 경로가 남아있는 경우, 재귀적으로 RouteParameters<Next>를 호출하여 다음 경로의 파라미터를 처리합니다.

    이 코드는 TypeScript의 많은 기능을 사용하여 유연하고 복잡한 라우팅 시스템에서 경로 파라미터를 추출하는 데 사용될 수 있습니다.

Infer 키워드

infer는 TypeScript에서 사용되는 키워드로, 주로 제네릭 타입에서 타입 매개변수의 일부를 추출하는 데 사용됩니다.

여기서 infer Rest는 타입 추론(inference)을 통해 나머지 부분을 추출하라는 의미입니다. 이것은 주로 템플릿 리터럴 타입을 다룰 때 유용하게 사용됩니다.

예를 들어, ${string}:${infer Rest}에서 ${string} 부분은 문자열이고, Rest는 나머지 부분을 나타냅니다. 그리고 이 Rest는 다시 다음 경로에 대한 정보를 담고 있는 것으로 추론됩니다.

type ExtractRest<T extends string> = T extends `${string}:${infer Rest}` ? Rest : never;

const example: ExtractRest<"users/:userId/posts/:postId">;
// 위의 예제에서는 "userId/posts/:postId"가 추출됨
profile
BackEnd Developer

0개의 댓글