Supabase 타입스크립트 완벽 가이드 : 타입 생성부터 활용까지

위키 · 2025. 5. 14.

배경

Firebase 대체제로 주목받고 있는 SupabasePostgSQL에 기반한 오픈소스 프로젝트입니다. 개발자는 Supabase를 사용하여 보다 빠르게 인증, DB 등을 구현할 수 있습니다. Supabase는 데이터베이스 스키마에서 타입스크립트 타입을 추출할 수 있는 강력한 기능을 제공하며, 이는 개발자가 타입을 일일히 작성해야하는 수고로움을 줄여 매우 유용합니다. 이 글에서는 Supabase로 프로젝트를 진행하며 학습한 Supabase의 타입 생성 방법과 실제 활용법을 정리하고 공유합니다.

타입 생성하는 2가지 방법

대쉬보드에서 다운로드받기

  • Supabase 프로젝트 대시보드에 접속한 후, 좌측 메뉴에서 API Docs > TABLES AND VIEW > Introduction 선택
  • Generate and download types 버튼 클릭
  • 내 프로젝트 타입 파일에 추가 (예 : @types/database.types.ts)

Supabase CLI 사용하기(권장)

  1. Supabase CLI 설치하기

개발 의존성으로 supabase cli 설치

npm i supabase@">=1.8.1" --save-dev
  1. Supabase 로그인하기

안내에 따라 로그인 합니다

npx supabase login
  1. Supabase 프로젝트 초기화하기

이미 초기화를 완료했다면 생략해도 괜찮습니다.

npx supabase init
  1. 타입 생성하기

원격 프로젝트의 경우:

npx supabase gen types typescript --project-id "$PROJECT_REF" --schema public > database.types.ts
  • $PROJECT_REF: Supabase 프로젝트의 참조 ID입니다. (대시보드의 Project Settings > General 에서 확인 가능)
  • -schema public: public 스키마에 있는 테이블, 뷰, 함수 등에 대한 타입을 생성합니다. 만약 auth, storage 또는 사용자가 직접 만든 커스텀 스키마의 타입을 생성하고 싶다면, 쉼표로 구분하여 -schema public,auth,storage,custom_schema 와 같이 여러 스키마를 지정할 수 있습니다.
  • > src/database.types.ts: 생성된 타입을 저장할 경로와 파일명을 지정합니다. (예: src 폴더 아래)

로컬 개발 환경 대상: (Supabase를 로컬에서 Docker 등으로 실행 중일 때)

npx supabase gen types typescript --local > database.types.ts

생성된 타입둘러보기

위 방법을 실행하면 다음과 같은 타입 정의 파일(database.types.ts)이 생성됩니다.

export type Json = string | number | boolean | null | { [key: string]: Json | undefined } | Json[]

export interface Database {
  public: { 
    Tables: { 
      movies: { // 테이블 이름 (예: movies)
        Row: { // .select() 호출 시 반환되는 데이터의 타입
          id: number
          name: string
          data: Json | null
          created_at: string
        }
        Insert: { // .insert() 호출 시 전달해야 하는 데이터의 타입
          id?: never 
          name: string 
          data?: Json | null 
          created_at?: string 
        }
        Update: { // .update() 호출 시 전달해야 하는 데이터의 타입
          id?: never
          name?: string 
          data?: Json | null
          created_at?: string
        }
        Relationships: [ // 관계 정의 (Foreign Keys)
          // ... 관계 정의가 있다면 여기에 표시됨
        ]
      }
      // ... 다른 테이블들
    }
    Views: { // 뷰 목록 (View가 있다면)
    }
    Functions: { // 함수 목록 (함수가 있다면)
    }
    Enums: { // Enum 타입 목록 (Enum이 있다면)
    }
    CompositeTypes: { // 복합 타입 목록
    }
  }
}

...

활용법

Supabase 클라이언트에 타입 적용하기

생성된 Database 타입을 Supabase 클라이언트 초기화 시 제네릭으로 전달합니다.

import { createClient } from '@supabase/supabase-js'
import { Database } from './database.types'
const supabase = createClient<Database>(process.env.SUPABASE_URL, process.env.SUPABASE_ANON_KEY

이제 supabase 클라이언트를 사용하여 쿼리를 작성할 때, 테이블명, 컬럼명, 반환값 등에 대해 자동완성 및 타입 체크의 이점을 누릴 수 있습니다.

async function getMovieTitle(movieId: number) {
  const { data, error } = await supabase
    .from('movies') // 'movies' 자동완성
    .select('name') // 'name' 자동완성, 다른 컬럼도 가능
    .eq('id', movieId)
    .single();

  if (error) throw error;
  // data는 { name: string } | null 타입으로 추론됨
  return data?.name;
}

타입 추출하기

생성된 타입 파일에서 직접 필요한 타입을 추출하여 변수, 함수 파라미터, 반환 타입 등으로 명시적으로 사용할 수 있습니다.

import { type Database, type Tables, type Enums } from '../database.types'

type Movie = Tables<'movies'> // Database['public']['Tables']['movies']['Row'] 와 동일
let movie: Movie;

type UserStatus = Enums<'user_status'> // Database['public']['Enums']['user_status'] 와 동일
let currentUserStatus: UserStatus = "ACTIVE";

import { type TablesInsert, type TablesUpdate } from "../database.types";

type MovieInsert = TablesInsert<"movies"> // Tables<'movies'>['Insert']와 같음

function addMovie(newMovie: MovieInsert) {
  // ... supabase.from('movies').insert(newMovie)
}

조인 쿼리의 타입 구하기

테이블 간 조인(join)을 포함하는 복잡한 쿼리의 결과 타입을 정확하게 얻고 싶을 때 QueryData 헬퍼 타입을 사용할 수 있습니다.

// 테이블 구조
create table countries (
  id serial primary key,
  name text
);
create table cities (
  id serial primary key,
  name text,
  country_id int references countries(id)
);
import { type QueryData } from '@supabase/supabase-js'
// ... supabase 클라이언트 설정

const query = supabase.from('countries').select(`
  id,
  name,
  cities (
    id,
    name
  )
`);

// QueryData를 사용하여 query의 예상 결과 데이터 타입을 추출
type CountriesWithCities = QueryData<typeof query>;

async function getCountriesAndCities() {
  const { data, error } = await query;
  if (error) throw error;

  // 이제 data는 CountriesWithCities 타입으로 강력하게 타이핑됨
  const countries: CountriesWithCities = data;
  countries.forEach(country => {
    console.log(country.name); // 'name' 자동완성
    country.cities.forEach(city => { // 'cities' 자동완성 (배열)
      console.log(city.name); // city 안의 'name' 자동완성
    });
  });
}

이외에도 자동생성된 타입이 기대와 다를 때 수행할 수 있는 타입 오버라이드 등과 같은 고급 타입 활용이 공식문서에 있으므로, 포스팅을 다 읽고 꼭 공식문서를 확인해주세요.

참고자료 1 : Supabse : REST API > Generating TypeScript Types

타입 업데이트 자동화하기

1. package.json에 스크립트 추가하기

supabase gen types 명령어를 package.jsonscripts에 추가하여 쉽게 실행할 수 있도록 합니다.

// package.json
{
  "scripts": {
    "update-types": "supabase gen types typescript --project-id \"$PROJECT_REF\" --schema public > src/database.types.ts",
    "update-types:local": "supabase gen types typescript --local > src/database.types.ts"
    // ... 다른 스크립트들
  }
}
  • $PROJECT_REF는 실제 값으로 변경해줍니다.
  • 이제 npm run update-types (또는 yarn update-types) 명령으로 타입을 업데이트할 수 있습니다.
  • 개인 작업 시에는 데이터베이스 스키마 변경 후 이 명령어를 실행하는 것만으로도 충분할 수 있습니다.

2. 깃헙 Actions 사용하기

팀 단위로 작업하거나 주기적으로 자동 업데이트를 원한다면 GitHub Actions를 활용하는 것이 매우 효과적입니다.

  1. 깃헙 시크릿, 환경변수 설정
  2. 깃헙 워크 플로우 파일 생성
//.github/workflows/update-types.yml

name: Update Supabase Database Types

on:
  schedule:
    # 매일 자정(UTC)에 실행 (cron 표현식, 필요에 따라 수정)
    - cron: '0 0 * * *'
  workflow_dispatch: # 수동 실행도 가능하도록 추가

jobs:
  update:
    runs-on: ubuntu-latest
    permissions:
      contents: write # 리포지토리에 코드 push 권한 부여

    env:
      PROJECT_REF: ${{ secrets.PROJECT_REF }}
      SUPABASE_ACCESS_TOKEN: ${{ secrets.SUPABASE_ACCESS_TOKEN }}
      # 타입 파일 경로를 프로젝트에 맞게 수정
      TYPE_FILE_PATH: src/database.types.ts

    steps:
      - name: Checkout repository
        uses: actions/checkout@v4
        with:
          persist-credentials: false # github-actions[bot]으로 커밋하기 위함
          fetch-depth: 0 # 마지막 커밋 이후의 모든 변경사항을 가져오기 위함

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '20' # 프로젝트에 맞는 Node.js 버전 사용

      - name: Install Supabase CLI (and other dependencies if needed)
        run: npm install supabase@latest --save-dev # 또는 yarn add supabase@latest --dev
        # package-lock.json 또는 yarn.lock이 있다면 npm ci 또는 yarn install 권장

      - name: Update database types
        run: npx supabase gen types typescript --project-id "$PROJECT_REF" --schema public > $TYPE_FILE_PATH
        # 로컬 환경이라면: npx supabase gen types typescript --local > $TYPE_FILE_PATH

      - name: Check for file changes
        id: git_status
        run: |
          echo "status=$(git status -s $TYPE_FILE_PATH)" >> $GITHUB_OUTPUT

      - name: Commit and push if changes exist
        if: steps.git_status.outputs.status != '' # 변경 사항이 있을 때만 실행
        run: |
          git config --local user.email "41898282+github-actions[bot]@users.noreply.github.com"
          git config --local user.name "github-actions[bot]"
          git add $TYPE_FILE_PATH
          git commit -m "chore: Update database types" -a
          git push
        # 커밋 메시지는 팀 컨벤션에 맞게 수정

커뮤니티에서 지원하는 generate-supabase-db-types-github-action과 같은 액션을 사용하는 것도 좋은 선택입니다.

후기

Supabase의 타입 시스템을 깊이 사용해보면서, 특히 제네릭(Generics)의 엄청난 파워에 새삼 감탄할 수 있었습니다. createClient<Database>로 클라이언트 전체의 데이터베이스 구조를 인식시키는 것부터 시작해서, Tables<'테이블명'>이나 QueryData<typeof 쿼리> 같은 제네릭 헬퍼 타입을 활용하면서 매우 편리함을 느꼈습니다. “잘만든 제네릭 타입이 개발자를 편하게 한다”를 느낄 수 있었습니다.