types.ts 정리 — Supabase 연동부터 실전 활용까지
이 글에서 다루는 내용
types.ts는 단순히 DB 스키마를 정의하는 파일이 아니라, 프로젝트 전반에서 사용하는 TypeScript 타입을 한곳에 모아두는 역할을 합니다. 이 글에서는 Supabase와 연동해 타입을 자동 생성하는 방법부터, API 응답·폼·UI 상태·외부 서비스·유틸리티·컴포넌트 Props까지 실전에서 자주 쓰이는 다양한 활용 패턴을 정리합니다.
Supabase 연동으로 types.ts 자동 생성하기
Supabase CLI를 사용하면 DB 스키마를 기반으로 types.ts를 자동으로 생성할 수 있습니다. 수동으로 작성한 타입과 달리 컬럼 추가·삭제 시 명령어 한 번으로 동기화가 가능해 유지보수가 훨씬 편해집니다.
1단계 — Supabase CLI 설치 (최초 1회)
# npm
npm install -g supabase
# macOS (Homebrew)
brew install supabase/tap/supabase
2단계 — Supabase 로그인 (최초 1회)
supabase login
명령 실행 후 브라우저가 열리면 Supabase 계정으로 인증합니다.
3단계 — 프로젝트 Reference ID 확인
Supabase Dashboard에서 아래 경로로 이동해 Reference ID를 복사합니다.
Project Settings → General → Reference ID
예: abcdefghijklmno
4단계 — 타입 생성 명령 실행
npx supabase gen types typescript --project-id <Reference-ID> > src/lib/supabase/types.ts
# 실제 예시
npx supabase gen types typescript --project-id abcdefghijklmno > src/lib/supabase/types.ts
현재 수동으로 작성한 src/lib/supabase/types.ts가 DB 스키마 기반으로 자동 덮어쓰기됩니다.
언제 다시 실행해야 할까?
- 테이블 컬럼 추가 또는 삭제 시
- 새 마이그레이션 적용 후
Database타입에서 빨간 줄이 뜰 때
로컬 개발 환경 연결 (선택)
프로젝트 루트에서 아래 명령어를 실행해 프로젝트를 연결해두면 이후에는 --project-id 없이 간단하게 실행할 수 있습니다.
supabase init # 최초 1회
supabase link --project-ref <Reference-ID> # 최초 1회
# 이후부터는 아래 명령어만
npx supabase gen types typescript --linked > src/lib/supabase/types.ts
package.json 스크립트 등록 (권장)
팀 작업 시 명령어를 매번 입력하는 번거로움을 줄이려면 스크립트로 등록해두는 것이 좋습니다.
"scripts": {
"gen:types": "supabase gen types typescript --linked > src/lib/supabase/types.ts"
}
이후에는 npm run gen:types만 실행하면 됩니다.
지금
types.ts를 수동으로 작성해둔 상태라도 빌드·실행에는 문제없습니다. Supabase 프로젝트 생성 후 위 명령어를 한 번 실행해 자동 생성된 파일로 교체하면 깔끔하게 정리됩니다.
types.ts의 다양한 활용 패턴
DB 스키마 타입 외에도 types.ts는 프로젝트 전반의 다양한 타입을 담는 공간으로 활용됩니다.
유니온 타입 / 리터럴 타입
상태나 옵션처럼 값이 정해진 경우 유니온 타입으로 명확하게 표현합니다.
export type Theme = "light" | "dark" | "system"
export type Language = "ko" | "en" | "ja"
export type SortOrder = "asc" | "desc"
공통 API 응답 래퍼
API 응답 형태를 제네릭으로 추상화하면 일관된 처리가 가능합니다.
export interface ApiResponse<T> {
data: T | null
error: string | null
message?: string
}
export interface PaginatedResponse<T> {
data: T[]
total: number
page: number
limit: number
hasNext: boolean
}
폼 관련 타입
입력값과 유효성 에러를 타입으로 명확하게 정의합니다.
export interface SubscriptionForm {
name: string
amount: number
cycle: Cycle
next_payment_date: string
category: Category
memo?: string
}
export type FormError<T> = Partial<Record<keyof T, string>>
UI 상태 타입
모달, 토스트 등 UI 상태도 타입으로 정의하면 Props 전달 시 실수를 줄일 수 있습니다.
export interface ModalState {
isOpen: boolean
type: "add" | "edit" | "delete" | null
targetId?: number
}
export interface ToastState {
message: string
type: "success" | "error" | "info"
duration?: number
}
외부 서비스 타입 (Toss Payments 등)
외부 API와 연동할 때 요청·응답 타입을 직접 정의합니다.
export interface TossPaymentConfirmRequest {
paymentKey: string
orderId: string
amount: number
}
export interface TossPaymentConfirmResponse {
paymentKey: string
orderId: string
orderName: string
method: string
totalAmount: number
status: "DONE" | "CANCELED" | "PARTIAL_CANCELED"
approvedAt: string
}
유틸리티 타입 확장
프로젝트 전반에서 반복적으로 쓰이는 타입 패턴을 미리 정의해 재사용합니다.
export type Nullable<T> = T | null
export type Optional<T> = T | undefined
export type Maybe<T> = T | null | undefined
// 특정 키만 필수로 만들기
export type RequireFields<T, K extends keyof T> = T & Required<Pick<T, K>>
// 타임스탬프 키 제외
export type OmitTimestamps<T> = Omit<T, "created_at" | "updated_at">
컴포넌트 Props 타입
컴포넌트의 Props를 별도 타입으로 정의하면 재사용성과 가독성이 높아집니다.
export interface ButtonProps {
label: string
variant: "primary" | "secondary" | "ghost"
size?: "sm" | "md" | "lg"
disabled?: boolean
onClick?: () => void
}
export interface SubscriptionCardProps {
subscription: Database["public"]["Tables"]["subscriptions"]["Row"]
onEdit: (id: number) => void
onDelete: (id: number) => void
}
파일 구조 권장 방식
규모가 커질수록 도메인별로 파일을 분리하는 것이 관리하기 편합니다.
src/
lib/supabase/types.ts # DB 스키마 전용 (자동 생성)
types/
index.ts # 공통 타입 모음
api.ts # API 요청/응답 타입
ui.ts # UI 상태 타입
payment.ts # 결제 관련 타입