Docs of Jace-Lab

DrizzleORM

DrizzleORM documentation

최근 발생한 이슈들을 해결하며 쌓은 기술적 맥락을 바탕으로, Drizzle ORM을 가장 효율적으로 구축하고 사용하는 가이드를 정리해 드립니다. Jace님의 모노레포 및 Supabase 환경에 최적화된 구성입니다.


1. 설치 (Installation)

Drizzle은 핵심 로직인 ORM과 개발 도구인 Kit으로 나뉩니다. PostgreSQL 환경을 기준으로 설치합니다.

# 1. 핵심 패키지 및 드라이버 설치
pnpm add drizzle-orm postgres

# 2. 개발 도구 및 환경 변수 관리 도구 설치
pnpm add -D drizzle-kit dotenv-cli
  • postgres: PostgreSQL용 경량 드라이버입니다.
  • dotenv-cli: 아까 겪으셨던 .env 로드 및 ESM 충돌 문제를 깔끔하게 해결해 줍니다.

2. 프로젝트 설정 (Configuration)

프로젝트 루트에 drizzle.config.ts를 생성합니다. 모노레포와 6543/5432 포트 이슈를 고려한 설정입니다.

import { defineConfig } from "drizzle-kit";

export default defineConfig({
  // 스키마 파일 위치 (글로브 패턴 권장)
  schema: "./src/**/schema.ts",
  // 마이그레이션 파일이 저장될 폴더
  out: "./drizzle",
  dialect: "postgresql",
  dbCredentials: {
    // 팁: push/pull 시에는 5432(Direct) 포트 사용 권장
    url: process.env.DIRECT_URL || process.env.DATABASE_URL!,
  },
  // DB 컬럼명을 snake_case로 자동 변환 (에러 방지 핵심)
  casing: "snake_case",
  // 특정 스키마만 관리 (Supabase 시스템 테이블 제외)
  schemaFilter: ["public"],
});

3. 스키마 정의 (Schema Definition)

TypeScript의 타입을 그대로 살려 테이블을 정의합니다.

import { pgTable, uuid, text, boolean, timestamp } from "drizzle-orm/pg-core";
import { uuidv7 } from "uuidv7";

export const user = pgTable("user", {
  id: uuid("id").primaryKey().$defaultFn(() => uuidv7()),
  email: text("email").notNull().unique(),
  name: text("name").notNull(),
  isPremium: boolean("is_premium").default(false).notNull(),
  createdAt: timestamp("created_at", { withTimezone: true, mode: "date" }).defaultNow().notNull(),
});

4. DB 연결 (Connection)

애플리케이션에서 DB를 조작하기 위한 인스턴스를 생성합니다.

import { drizzle } from "drizzle-orm/postgres-js";
import postgres from "postgres";
import * as schema from "./schema";

const queryClient = postgres(process.env.DATABASE_URL!);
export const db = drizzle(queryClient, { schema });

5. 워크플로우 (Push vs Migrate)

Drizzle의 가장 큰 장점은 유연한 워크플로우입니다. 상황에 맞춰 선택하세요.

방법 A: db:push (빠른 프로토타이핑)

스키마 파일을 수정하면 즉시 DB에 반영합니다. 개발 초기 단계에 유리합니다.

# package.json 스크립트 등록 권장
"db:push": "dotenv -e ../../.env -e .env -- drizzle-kit push"

방법 B: generate & migrate (운영 환경 권장)

변경 사항을 SQL 파일로 기록하고 순차적으로 적용합니다.

  1. drizzle-kit generate: 변경사항을 drizzle/ 폴더에 .sql 파일로 생성.
  2. drizzle-kit migrate: 생성된 SQL을 실제 DB에 실행.

6. CRUD 사용법 (Usage)

데이터 삽입 (Insert)

await db.insert(user).values({
  name: "Jace",
  email: "jace@example.com",
});

데이터 조회 (Select)

// 1. 기본 방식
const result = await db.select().from(user).where(eq(user.name, "Jace"));

// 2. Relational Query (권장: 직관적임)
const userData = await db.query.user.findFirst({
  where: (user, { eq }) => eq(user.id, "some-uuid"),
  with: { posts: true } // 관계 설정 시 사용
});

데이터 수정/삭제 (Update/Delete)

await db.update(user).set({ isPremium: true }).where(eq(user.id, "id"));
await db.delete(user).where(eq(user.id, "id"));

7. 실전 팁 (Best Practices)

1. 포트 구분 사용 Supabase를 쓴다면 6543(Transaction Pooler)은 서버 실행용으로, 5432(Direct)는 Drizzle Kit 전용(DIRECT_URL)으로 쓰세요. 아까 겪으신 undefined (reading 'replace') 에러를 막는 가장 확실한 방법입니다.

2. casing: "snake_case" 설정 코드에서는 camelCase를 쓰고 DB에서는 snake_case를 쓰는 것이 표준입니다. drizzle.config.ts에 이 옵션을 넣으면 일일이 컬럼명을 지정하지 않아도 되어 코드가 훨씬 간결해집니다.

3. updatedAt 자동화 PostgreSQL 레벨에서 트리거를 쓰거나, Drizzle의 $onUpdateFn을 사용하여 new Date()가 들어가도록 설정하세요.

On this page