Skip to Content
WebTypeScriptTypeScript 중급04. 템플릿 리터럴 타입으로 API 경로 만들기

이번 편의 결과물: lib/api/routes.ts/api/transactions/${string} 형태의 라우트 타입과 필터 쿼리 문자열 타입을 정의하고, 실제 경로 문자열을 만드는 함수를 작성합니다. · 다루는 개념: 템플릿 리터럴 타입 문법, 유니온과 결합해 문자열 집합 만들기, 내장 문자열 조작 타입(Uppercase 등)

이 편에서 만드는 파일

expense-tracker/src/lib/ └── api/ └── routes.ts + (TransactionRoute, TransactionListRoute, HttpMethodLabel)

개념 정리

템플릿 리터럴 타입 문법

문자열 리터럴 타입 안에 다른 타입을 ${}로 끼워 넣으면, 그 자리에 올 수 있는 모든 조합이 하나의 문자열 타입으로 계산됩니다.

type Greeting = `안녕, ${string}` const a: Greeting = '안녕, 세계' // 허용

유니온과 결합해 문자열 집합 만들기

${} 안에 유니온을 넣으면 각 멤버로 만들 수 있는 모든 문자열 조합이 다시 유니온으로 펼쳐집니다.

type Method = 'get' | 'post' type Action = `${Method}Transaction` // 'getTransaction' | 'postTransaction'

라우트 경로도 같은 방식으로 만듭니다. 거래 하나를 가리키는 경로는 고정된 접두사 뒤에 아무 문자열 id나 올 수 있는 형태입니다.

type TransactionRoute = '/api/transactions' | `/api/transactions/${string}`

내장 문자열 조작 타입

TypeScript는 문자열 리터럴 타입의 대소문자를 바꿔주는 내장 타입 네 가지를 제공합니다.

타입하는 일
Uppercase<T>전체를 대문자로
Lowercase<T>전체를 소문자로
Capitalize<T>첫 글자만 대문자로
Uncapitalize<T>첫 글자만 소문자로
type HttpMethod = 'get' | 'post' type HttpMethodLabel = Uppercase<HttpMethod> // 'GET' | 'POST'

실습

1. api 폴더에 routes.ts 만들기

expense-tracker/src/lib/api/ 폴더를 만들고 routes.ts 파일을 추가합니다.

2. 단일 리소스 라우트 타입과 생성 함수

// expense-tracker/src/lib/api/routes.ts export type TransactionRoute = '/api/transactions' | `/api/transactions/${string}` export function transactionRoute(id?: string): TransactionRoute { return id === undefined ? '/api/transactions' : `/api/transactions/${id}` }

3. 필터 쿼리 문자열 타입 정의

06·07편에서 만든 TransactionFilter({ category: string } | { month: string })는 12편의 화면 필터가 종류(수입/지출) 기준으로 바뀌면서 화면에서는 더 이상 쓰이지 않았습니다. 이 편부터는 같은 모양을 API 쪽 쿼리 조건으로 재사용합니다.

// expense-tracker/src/lib/api/routes.ts (이어서 추가) import type { TransactionFilter } from '../parsing.ts' export type TransactionListQuery = `category=${string}` | `month=${string}` export type TransactionListRoute = '/api/transactions' | `/api/transactions?${TransactionListQuery}` export function transactionListRoute(filter: TransactionFilter | null): TransactionListRoute { if (filter === null) { return '/api/transactions' } if ('category' in filter) { return `/api/transactions?category=${filter.category}` } return `/api/transactions?month=${filter.month}` }

4. HTTP 메서드 라벨 타입

// expense-tracker/src/lib/api/routes.ts (이어서 추가) export type HttpMethod = 'get' | 'post' | 'patch' | 'delete' export type HttpMethodLabel = Uppercase<HttpMethod>

HttpMethodLabel은 09편에서 요청 함수의 로그 문구를 만들 때 다시 씁니다.

5. 타입이 실제로 문자열 형태를 제한하는지 확인

// 임시로 아래 줄을 추가해봅니다(확인 후 지웁니다) const wrong: TransactionRoute = '/transactions'
npx tsc --noEmit src/lib/api/routes.ts:20:7 - error TS2322: Type '"/transactions"' is not assignable to type 'TransactionRoute'.

/api/ 접두사가 빠진 문자열은 TransactionRoute가 허용하는 두 형태 중 어느 쪽에도 맞지 않아 그 자리에서 오류가 납니다. 확인이 끝나면 임시로 추가한 줄을 지웁니다.

6. 실행

npx tsc --noEmit

확인

  • npx tsc --noEmit이 임시 오류 코드를 지운 뒤 오류 없이 통과한다.
  • 편집기에서 transactionRoute('abc123')의 반환 타입에 마우스를 올리면 TransactionRoute로 표시된다.
  • HttpMethodLabel 타입을 열어보면 'GET' | 'POST' | 'PATCH' | 'DELETE' 네 가지로 펼쳐져 있다.

직접 해보기

  1. TransactionListQuery에 정렬 조건(sort=${'date' | 'amount'})을 유니온 멤버로 추가하고, TransactionListRoute가 허용하는 문자열 집합이 어떻게 늘어나는지 편집기에서 확인해보세요.
  2. transactionListRoute({ category: 'food' }), transactionListRoute(null)을 각각 호출해 반환값을 확인해보세요.

정답 보기

export type TransactionListQuery = `category=${string}` | `month=${string}` | `sort=${'date' | 'amount'}`

이 유니온에 멤버를 추가하면 TransactionListRoute도 함께 넓어져, /api/transactions?sort=date, /api/transactions?sort=amount 같은 문자열도 허용됩니다.

transactionListRoute({ category: 'food' })'/api/transactions?category=food'를, transactionListRoute(null)'/api/transactions'를 반환합니다.

자주 하는 실수

증상원인고치는 법
라우트 문자열에 접두사를 빠뜨려 타입 오류하드코딩한 문자열이 템플릿 리터럴 타입 형태와 다름반드시 transactionRoute, transactionListRoute 함수를 거쳐 문자열을 만든다
Capitalize 등을 값에 직접 적용하려 함문자열 조작 타입은 타입에만 적용되고 런타임 값에는 적용되지 않음런타임에 대소문자를 바꾸려면 toUpperCase() 같은 문자열 메서드를 쓴다
TransactionFilter를 화면 필터 UI와 혼동12편 이후 화면 필터는 종류 기준으로 바뀐 사실을 놓침TransactionFilter는 이제 API 쿼리 조건 전용이라는 점을 기억한다

확인 문제

문제 14지선다
템플릿 리터럴 타입에서 유니온을 끼워 넣었을 때 일어나는 일은
문제 24지선다
Uppercase<HttpMethod>가 하는 일은
문제 34지선다
이 편에서 06·07편의 TransactionFilter 타입을 다시 가져다 쓴 이유는
문제 44지선다
const wrong: TransactionRoute = /transactions처럼 접두사를 빠뜨린 문자열을 대입했을 때 생기는 일은

참고 자료

Last updated on