Devlery
Blog/Drizzle

Drizzle ORM: SQL을 아는 개발자를 위한 TypeScript ORM

SQL 친화적이고 가벼운 TypeScript ORM, Drizzle의 스키마 정의부터 쿼리 작성, 마이그레이션까지 핵심 사용법을 알아봅니다.

TypeScript에서 데이터베이스를 다룰 때 가장 먼저 떠오르는 ORM은 Prisma다. 스키마 파일로 모델을 정의하고, 코드를 생성하고, 타입 안전한 쿼리를 작성할 수 있다. 훌륭한 도구다.

하지만 Prisma를 쓰다 보면 가끔 답답한 순간이 온다. 복잡한 조인을 작성하고 싶은데 Prisma의 API로는 표현이 어렵다거나, 생성된 클라이언트의 크기가 서버리스 환경에서 부담이 된다거나, .prisma 스키마 파일이 TypeScript가 아니라 별도 문법이라 불편하다거나.

Drizzle은 이런 불만에서 출발한 ORM이다.

Drizzle이 뭔가요?

Drizzle은 TypeScript로 스키마를 정의하고, SQL에 가까운 문법으로 쿼리를 작성하는 ORM이다. 핵심 철학은 간단하다.

SQL을 알면 Drizzle을 안다.

별도의 스키마 언어가 없다. 코드 생성 과정도 없다. TypeScript 파일에 스키마를 정의하면 그것이 곧 타입이 되고, 쿼리 빌더가 된다.

번들 크기도 인상적이다. minified + gzipped 기준 약 7.4KB 로, 의존성은 정확히 0개다.

설치

PostgreSQL을 사용하는 경우를 기준으로 설명한다.

npm install drizzle-orm postgres
npm install -D drizzle-kit

drizzle-orm은 런타임 ORM 코어이고, drizzle-kit은 마이그레이션 등 개발 도구를 담당한다.

스키마 정의

Drizzle의 스키마는 그냥 TypeScript 파일이다.

// src/db/schema.ts
import { pgTable, serial, text, timestamp, integer } from 'drizzle-orm/pg-core'

export const users = pgTable('users', {
  id: serial('id').primaryKey(),
  name: text('name').notNull(),
  email: text('email').notNull().unique(),
  createdAt: timestamp('created_at').defaultNow(),
})

export const posts = pgTable('posts', {
  id: serial('id').primaryKey(),
  title: text('title').notNull(),
  content: text('content'),
  authorId: integer('author_id').references(() => users.id),
  createdAt: timestamp('created_at').defaultNow(),
})

Prisma의 .prisma 파일과 달리 일반 TypeScript이기 때문에, IDE의 자동완성, 리팩토링, import 관리가 모두 그대로 동작한다.

데이터베이스 연결

// src/db/index.ts
import { drizzle } from 'drizzle-orm/postgres-js'
import postgres from 'postgres'
import * as schema from './schema'

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

쿼리 작성

Drizzle의 쿼리는 SQL을 거의 그대로 옮겨놓은 것처럼 보인다.

import { eq, and, gt, desc } from 'drizzle-orm'
import { db } from './db'
import { users, posts } from './db/schema'

// SELECT
const allUsers = await db.select().from(users)

// WHERE
const user = await db
  .select()
  .from(users)
  .where(eq(users.email, 'hong@example.com'))

// INSERT
await db.insert(users).values({
  name: '홍길동',
  email: 'hong@example.com',
})

// UPDATE
await db
  .update(users)
  .set({ name: '김철수' })
  .where(eq(users.id, 1))

// DELETE
await db.delete(users).where(eq(users.id, 1))

SQL의 SELECT ... FROM ... WHERE ... 구조가 그대로 보인다. ORM을 배우는 게 아니라 SQL을 TypeScript로 쓰는 느낌이다.

조인

Drizzle의 진가는 조인에서 드러난다.

const postsWithAuthor = await db
  .select({
    postTitle: posts.title,
    authorName: users.name,
  })
  .from(posts)
  .leftJoin(users, eq(posts.authorId, users.id))
  .where(gt(posts.createdAt, new Date('2026-01-01')))
  .orderBy(desc(posts.createdAt))

Prisma에서는 includeselect의 중첩으로 표현해야 하는 복잡한 조인이, Drizzle에서는 SQL을 아는 사람이라면 직관적으로 작성할 수 있다.

마이그레이션

drizzle-kit을 사용하면 스키마 변경사항을 기반으로 마이그레이션 파일을 자동 생성할 수 있다.

# 마이그레이션 파일 생성
npx drizzle-kit generate

# 마이그레이션 적용
npx drizzle-kit migrate

# DB 상태를 브라우저에서 확인 (Drizzle Studio)
npx drizzle-kit studio

drizzle-kit studio를 실행하면 브라우저에서 데이터베이스를 직접 조회하고 수정할 수 있는 GUI가 열린다. 별도 설치 없이 바로 쓸 수 있어서 편하다.

Prisma와 비교하면?

항목PrismaDrizzle
스키마 언어.prisma (자체 문법)TypeScript
코드 생성필요 (prisma generate)불필요
번들 크기수 MB~7.4KB
쿼리 스타일자체 APISQL 유사
조인include/select 중첩SQL 조인 문법
서버리스 적합성Cold start 부담가벼움
학습 곡선Prisma 문법 학습 필요SQL 알면 쉬움

Prisma가 "ORM을 사용하는 경험"을 추구한다면, Drizzle은 "SQL을 타입 안전하게 쓰는 경험"을 추구한다. 어떤 게 더 좋다기보다는 취향과 프로젝트 성격의 문제다.

마무리

Drizzle은 SQL을 좋아하지만 타입 안전성도 포기하고 싶지 않은 개발자를 위한 ORM이다. 서버리스 환경에서 Prisma의 번들 크기가 부담되거나, 복잡한 쿼리를 ORM의 추상화 없이 직접 작성하고 싶다면, Drizzle이 좋은 대안이 될 수 있다.

지원하는 데이터베이스도 PostgreSQL, MySQL, SQLite는 물론 Turso, Neon, PlanetScale, Cloudflare D1 같은 서버리스 데이터베이스까지 폭넓게 커버하고 있다. 특히 엣지 환경에서 데이터베이스를 다뤄야 한다면 Drizzle을 한 번 살펴보자.