🗄️
데이터베이스
Drizzle ORM
"드리즐"로 발음
TypeScript 네이티브 ORM으로, SQL에 가까운 문법으로 완벽한 타입 안전성을 제공합니다. 경량화되어 서버리스/엣지 환경에 최적화되었으며, "If you know SQL, you know Drizzle"이라는 철학을 따릅니다.
"드리즐"로 발음
TypeScript 네이티브 ORM으로, SQL에 가까운 문법으로 완벽한 타입 안전성을 제공합니다. 경량화되어 서버리스/엣지 환경에 최적화되었으며, "If you know SQL, you know Drizzle"이라는 철학을 따릅니다.
Drizzle ORM은 2022년에 등장한 TypeScript/JavaScript용 ORM으로, Prisma의 무거운 런타임과 달리 순수 TypeScript로 작성되어 번들 사이즈가 작고 Cold Start가 빠릅니다. SQL 문법에 가까운 API를 제공하여 SQL 지식이 있으면 쉽게 사용할 수 있으며, 생성된 쿼리가 예측 가능합니다.
SQL 문법을 TypeScript로 1:1 매핑하여, ORM 추상화로 인한 예측 불가능한 쿼리 생성을 방지합니다.
| 특성 | Drizzle | Prisma |
|---|---|---|
| 번들 사이즈 | ~7KB (경량) | ~2MB+ (무거움) |
| Cold Start | 빠름 (서버리스 적합) | 느림 (Engine 로드) |
| 쿼리 스타일 | SQL-like (예측 가능) | Prisma Client (추상화) |
| 스키마 정의 | TypeScript 코드 | .prisma 파일 |
| Raw SQL | 타입 안전 지원 | $queryRaw (제한적) |
| 지원 DB | PostgreSQL, MySQL, SQLite | 다양한 DB 지원 |
import { pgTable, serial, text, timestamp, integer, boolean, varchar } from 'drizzle-orm/pg-core';
import { relations } from 'drizzle-orm';
// 사용자 테이블
export const users = pgTable('users', {
id: serial('id').primaryKey(),
email: varchar('email', { length: 255 }).notNull().unique(),
name: text('name').notNull(),
role: varchar('role', { length: 20 }).default('user'),
createdAt: timestamp('created_at').defaultNow(),
updatedAt: timestamp('updated_at').defaultNow(),
});
// 게시글 테이블
export const posts = pgTable('posts', {
id: serial('id').primaryKey(),
title: varchar('title', { length: 255 }).notNull(),
content: text('content'),
published: boolean('published').default(false),
authorId: integer('author_id').references(() => users.id),
createdAt: timestamp('created_at').defaultNow(),
});
// 댓글 테이블
export const comments = pgTable('comments', {
id: serial('id').primaryKey(),
content: text('content').notNull(),
postId: integer('post_id').references(() => posts.id),
authorId: integer('author_id').references(() => users.id),
createdAt: timestamp('created_at').defaultNow(),
});
// 관계 정의
export const usersRelations = relations(users, ({ many }) => ({
posts: many(posts),
comments: many(comments),
}));
export const postsRelations = relations(posts, ({ one, many }) => ({
author: one(users, {
fields: [posts.authorId],
references: [users.id],
}),
comments: many(comments),
}));import { drizzle } from 'drizzle-orm/node-postgres';
import { eq, and, or, like, desc, asc, sql } from 'drizzle-orm';
import { Pool } from 'pg';
import * as schema from './schema';
const pool = new Pool({ connectionString: process.env.DATABASE_URL });
const db = drizzle(pool, { schema });
// CREATE - 단일 삽입
async function createUser(email: string, name: string) {
const [user] = await db.insert(schema.users)
.values({ email, name })
.returning();
return user;
}
// CREATE - 다중 삽입
async function createManyPosts(posts: typeof schema.posts.$inferInsert[]) {
return await db.insert(schema.posts)
.values(posts)
.returning();
}
// READ - 조건부 조회
async function getPublishedPosts(authorId?: number) {
return await db.select()
.from(schema.posts)
.where(
and(
eq(schema.posts.published, true),
authorId ? eq(schema.posts.authorId, authorId) : undefined
)
)
.orderBy(desc(schema.posts.createdAt))
.limit(10);
}
// READ - JOIN 쿼리 (SQL-like)
async function getPostsWithAuthor() {
return await db.select({
postId: schema.posts.id,
title: schema.posts.title,
authorName: schema.users.name,
authorEmail: schema.users.email,
})
.from(schema.posts)
.leftJoin(schema.users, eq(schema.posts.authorId, schema.users.id))
.where(eq(schema.posts.published, true));
}
// UPDATE
async function updatePost(postId: number, data: Partial) {
const [updated] = await db.update(schema.posts)
.set({ ...data, updatedAt: new Date() })
.where(eq(schema.posts.id, postId))
.returning();
return updated;
}
// DELETE
async function deletePost(postId: number) {
await db.delete(schema.posts)
.where(eq(schema.posts.id, postId));
} // Drizzle Query API - Prisma-like 문법도 지원
async function getPostWithRelations(postId: number) {
return await db.query.posts.findFirst({
where: eq(schema.posts.id, postId),
with: {
author: true,
comments: {
with: {
author: true,
},
orderBy: [desc(schema.comments.createdAt)],
limit: 10,
},
},
});
}
// 복잡한 필터링
async function searchPosts(query: string, filters: {
published?: boolean;
authorId?: number;
fromDate?: Date;
}) {
return await db.query.posts.findMany({
where: and(
like(schema.posts.title, `%${query}%`),
filters.published !== undefined
? eq(schema.posts.published, filters.published)
: undefined,
filters.authorId
? eq(schema.posts.authorId, filters.authorId)
: undefined,
filters.fromDate
? sql`${schema.posts.createdAt} >= ${filters.fromDate}`
: undefined
),
with: {
author: {
columns: { name: true, email: true },
},
},
orderBy: [desc(schema.posts.createdAt)],
});
}// 트랜잭션
async function transferCredits(fromUserId: number, toUserId: number, amount: number) {
return await db.transaction(async (tx) => {
// 잔액 확인
const [fromUser] = await tx.select()
.from(schema.users)
.where(eq(schema.users.id, fromUserId))
.for('update'); // SELECT FOR UPDATE
if (fromUser.credits < amount) {
throw new Error('Insufficient credits');
}
// 차감
await tx.update(schema.users)
.set({ credits: sql`credits - ${amount}` })
.where(eq(schema.users.id, fromUserId));
// 추가
await tx.update(schema.users)
.set({ credits: sql`credits + ${amount}` })
.where(eq(schema.users.id, toUserId));
return { success: true };
});
}
// 타입 안전한 Raw SQL
async function getPostStats() {
const result = await db.execute<{
author_id: number;
author_name: string;
post_count: number;
avg_comments: number;
}>(sql`
SELECT
u.id as author_id,
u.name as author_name,
COUNT(p.id) as post_count,
AVG(comment_counts.cnt)::numeric as avg_comments
FROM users u
LEFT JOIN posts p ON u.id = p.author_id
LEFT JOIN (
SELECT post_id, COUNT(*) as cnt
FROM comments
GROUP BY post_id
) comment_counts ON p.id = comment_counts.post_id
GROUP BY u.id, u.name
ORDER BY post_count DESC
`);
return result.rows;
}import type { Config } from 'drizzle-kit';
export default {
schema: './src/db/schema.ts',
out: './drizzle',
driver: 'pg',
dbCredentials: {
connectionString: process.env.DATABASE_URL!,
},
} satisfies Config;# 스키마 변경 감지 및 마이그레이션 파일 생성
npx drizzle-kit generate:pg
# 마이그레이션 적용
npx drizzle-kit push:pg
# DB 스키마 시각화 (Drizzle Studio)
npx drizzle-kit studio
# 기존 DB에서 스키마 추출
npx drizzle-kit introspect:pgPrisma에 비해 플러그인, 통합, 튜토리얼이 적습니다. 복잡한 케이스에서 참고할 자료가 부족할 수 있습니다.
SQL에 가까운 API이므로 SQL을 모르면 러닝 커브가 있습니다. Prisma처럼 SQL 없이 ORM만으로 해결하기 어렵습니다.
Drizzle Kit은 기본적인 마이그레이션을 지원하지만, Prisma Migrate처럼 정교한 다운그레이드나 데이터 마이그레이션 기능은 제한적입니다.