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-clipostgres: 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 파일로 기록하고 순차적으로 적용합니다.
drizzle-kit generate: 변경사항을drizzle/폴더에.sql파일로 생성.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()가 들어가도록 설정하세요.