Drizzle ORM 완전 가이드 | TypeScript로 타입 안전한 SQL 작성하기
이 글의 핵심
Prisma의 강력한 대안 Drizzle ORM. SQL과 거의 동일한 문법으로 TypeScript 타입 안전성을 제공하며, 번들 크기는 Prisma의 1/10, 성능은 2배 빠릅니다. Cloudflare Workers·Vercel Edge에서도 사용 가능합니다.
이 글의 핵심
Drizzle ORM은 Prisma보다 가볍고 빠른 TypeScript ORM입니다. SQL과 거의 동일한 문법, 완벽한 타입 추론, Edge Runtime 지원으로 차세대 표준을 제시하며, 번들 크기는 Prisma의 1/10, 성능은 2배 빠릅니다.
목차
Drizzle ORM이란?
Drizzle ORM은 2022년 출시된 TypeScript 우선 ORM으로, SQL의 단순함과 TypeScript의 타입 안전성을 결합합니다.
핵심 철학과 아키텍처
Drizzle이 추구하는 방향은 한 문장으로 요약할 수 있다. “ORM이 SQL을 가리지 않는다”는 것이다. Prisma는 스키마·클라이언트·마이그레이션을 한 생태계로 묶어 생산성을 올리고, TypeORM은 데코레이터와 리포지토리 패턴으로 객체 지향 쪽에 가깝다. 반면 Drizzle은 빌드 타임에 TypeScript로 스키마를 기술하고, 런타임에는 쿼리 빌더가 SQL에 최대한 가깝게 매핑하는 구조다. 덕분에 디버깅할 때 “실제로 어떤 SQL이 나갔는지”를 추적하기 쉽고, 복잡한 리포트 쿼리·윈도 함수·DB 특화 문법을 포기할 이유가 줄어든다.
아키텍처 측면에서 Drizzle은 드라이버 어댑터와 코어(스키마·drizzle-orm API)를 분리해 두었다. drizzle-orm/node-postgres, drizzle-orm/better-sqlite3, drizzle-orm/d1처럼 엔트리가 나뉘는 이유가 바로 이것이다. 애플리케이션 코드는 동일한 db.select().from() 패턴을 유지하되, 배포 대상(전통 Node 서버, serverless, Edge, 임베디드 SQLite)에 맞는 얇은 연결층만 갈아끼운다. 바이너리 네이티브 의존을 피하려는 설계는 Cloudflare D1·Turso 같은 Edge·LibSQL 시나리오와 궁합이 좋다. 다만 “올인원” 경험은 Prisma에 비해 도구 체인을 직접 이어붙이는 느낌이 남는다. 이는 단점이자 유연성이기도 하다.
🚀 핵심 특징
1. SQL과 유사한 문법
// Drizzle (SQL과 거의 동일)
const users = await db
.select()
.from(usersTable)
.where(eq(usersTable.age, 25));
// 실제 SQL
SELECT * FROM users WHERE age = 25;
2. 완벽한 타입 추론
// 타입이 자동으로 추론됨
const user = await db.select().from(users).limit(1);
// user는 { id: number; name: string; email: string }[]
3. 가볍고 빠름
Prisma:
- 번들 크기: 10MB+
- 바이너리 의존성
- Edge Runtime 불가
Drizzle:
- 번들 크기: 1MB
- 순수 TypeScript
- Edge Runtime 가능
4. 멀티 데이터베이스
- PostgreSQL
- MySQL
- SQLite
- Cloudflare D1
- Turso (LibSQL)
Drizzle 시작하기
1️⃣ 설치
# Drizzle ORM + PostgreSQL 드라이버
npm install drizzle-orm postgres
npm install -D drizzle-kit
# 또는 다른 DB
npm install drizzle-orm better-sqlite3 # SQLite
npm install drizzle-orm mysql2 # MySQL
2️⃣ 스키마 정의
// src/db/schema.ts
import { pgTable, serial, text, integer, timestamp } from 'drizzle-orm/pg-core';
export const users = pgTable('users', {
id: serial('id').primaryKey(),
name: text('name').notNull(),
email: text('email').notNull().unique(),
age: integer('age'),
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')
.notNull()
.references(() => users.id, { onDelete: 'cascade' }),
createdAt: timestamp('created_at').defaultNow(),
});
3️⃣ 데이터베이스 연결
// src/db/index.ts
import { drizzle } from 'drizzle-orm/postgres-js';
import postgres from 'postgres';
import * as schema from './schema';
const connectionString = process.env.DATABASE_URL!;
const client = postgres(connectionString);
export const db = drizzle(client, { schema });
CRUD 작업
📝 Create (INSERT)
// 단일 삽입
const newUser = await db.insert(users).values({
name: 'Alice',
email: '[email protected]',
age: 25,
}).returning();
console.log(newUser); // [{ id: 1, name: 'Alice', ... }]
// 여러 행 삽입
await db.insert(users).values([
{ name: 'Bob', email: '[email protected]', age: 30 },
{ name: 'Charlie', email: '[email protected]', age: 35 },
]);
🔍 Read (SELECT)
// 모든 사용자 조회
const allUsers = await db.select().from(users);
// WHERE 조건
import { eq, gt, like, and, or } from 'drizzle-orm';
const adults = await db
.select()
.from(users)
.where(gt(users.age, 18));
// 여러 조건
const result = await db
.select()
.from(users)
.where(
and(
gt(users.age, 18),
like(users.email, '%@gmail.com')
)
);
// 특정 필드만 선택
const names = await db
.select({ name: users.name, email: users.email })
.from(users);
// 정렬 및 페이징
const page = await db
.select()
.from(users)
.orderBy(users.createdAt)
.limit(10)
.offset(20);
✏️ Update (UPDATE)
// 단일 업데이트
await db
.update(users)
.set({ age: 26 })
.where(eq(users.id, 1));
// 여러 필드 업데이트
await db
.update(users)
.set({
name: 'Alice Smith',
age: 26,
})
.where(eq(users.email, '[email protected]'));
🗑️ Delete (DELETE)
// 조건부 삭제
await db
.delete(users)
.where(eq(users.id, 1));
// 여러 행 삭제
await db
.delete(users)
.where(gt(users.age, 100));
관계 (Relations)
스키마에서 관계 정의
// src/db/schema.ts
import { relations } from 'drizzle-orm';
export const users = pgTable('users', {
id: serial('id').primaryKey(),
name: text('name').notNull(),
});
export const posts = pgTable('posts', {
id: serial('id').primaryKey(),
title: text('title').notNull(),
authorId: integer('author_id')
.notNull()
.references(() => users.id),
});
// 관계 정의
export const usersRelations = relations(users, ({ many }) => ({
posts: many(posts),
}));
export const postsRelations = relations(posts, ({ one }) => ({
author: one(users, {
fields: [posts.authorId],
references: [users.id],
}),
}));
관계 쿼리
// 사용자와 게시글 함께 조회
const usersWithPosts = await db.query.users.findMany({
with: {
posts: true,
},
});
console.log(usersWithPosts);
// [
// {
// id: 1,
// name: 'Alice',
// posts: [
// { id: 1, title: 'First Post', authorId: 1 },
// { id: 2, title: 'Second Post', authorId: 1 },
// ]
// }
// ]
// 필터링된 관계
const userWithRecentPosts = await db.query.users.findFirst({
where: eq(users.id, 1),
with: {
posts: {
limit: 5,
orderBy: [desc(posts.createdAt)],
},
},
});
JOIN 쿼리
// INNER JOIN
const result = await db
.select({
userId: users.id,
userName: users.name,
postTitle: posts.title,
})
.from(users)
.innerJoin(posts, eq(users.id, posts.authorId));
// LEFT JOIN
const allUsersWithPosts = await db
.select()
.from(users)
.leftJoin(posts, eq(users.id, posts.authorId));
// 여러 테이블 JOIN
const comments = pgTable('comments', {
id: serial('id').primaryKey(),
content: text('content').notNull(),
postId: integer('post_id').references(() => posts.id),
userId: integer('user_id').references(() => users.id),
});
const fullData = await db
.select()
.from(posts)
.innerJoin(users, eq(posts.authorId, users.id))
.leftJoin(comments, eq(posts.id, comments.postId));
쿼리 빌더 심화: 집계·서브쿼리·조인
조인은 이미 SQL과 비슷한 흐름으로 읽히는 것이 Drizzle의 강점이다. 여기에 집계 함수(count, sum, avg, min, max)와 groupBy, having을 얹으면 대시보드·리포트 쿼리를 ORM 안에서 끝내기 쉽다.
import { count, desc, sql as dsql } from 'drizzle-orm';
// 게시글 수를 작성자별로 집계
const postsPerAuthor = await db
.select({
authorId: posts.authorId,
postCount: count(posts.id),
})
.from(posts)
.groupBy(posts.authorId)
.orderBy(desc(count(posts.id)));
// HAVING: 게시글 5개 이상인 작성자만
const prolific = await db
.select({ authorId: posts.authorId, c: count(posts.id) })
.from(posts)
.groupBy(posts.authorId)
.having(dsql`${count(posts.id)} >= 5`);
서브쿼리는 db.select().from(...).as('alias') 패턴으로 별칭을 만들고, 바깥 쿼리의 from/innerJoin에 끼워 넣는 방식이 흔하다. 복잡한 경우 Raw SQL 조각(sql\…`)과 병용하는 전략도 실무에서 자주 쓰인다. Drizzle은 “전부 빌더로만”을 강요하지 않는 편이라, 한 번은 빌더로 시도하고 한계가 보이면 **sql` 태그로 남은 부분만 덧붙이는 절충이 현실적이다.
import { sql } from 'drizzle-orm';
// 예: 최근 7일간 일별 게시글 수 (DB에 따라 date_trunc 문법 조정)
const daily = await db.execute(sql`
select date_trunc('day', ${posts.createdAt}) as day, count(*)::int as cnt
from ${posts}
where ${posts.createdAt} > now() - interval '7 days'
group by 1
order by 1
`);
db.query API는 관계 로딩에 편하지만, N+1을 스스로 최적화해 주는 마법은 기대하지 않는 것이 낫다. 목록 + 다건 연관 데이터가 필요하면 명시적 조인이나 배치 로딩 전략(id 모아서 inArray 한 번)을 설계해야 한다.
마이그레이션
스키마 정의와 마이그레이션 전략
프로덕션에서는 “스키마를 코드로만 관리할지”, “SQL 마이그레이션 파일을 리뷰할지”를 먼저 정하는 것이 안전하다. Drizzle Kit은 둘 다 지원한다. 팀이 작고 스테이징 DB에 실수해도 괜찮다면 drizzle-kit push로 스키마를 빠르게 맞출 수 있고, 규모가 커지면 generate로 SQL을 뽑아 PR에 올리고, CI에서 적용·검증하는 흐름이 일반적이다. 운영 DB에 직접 push하는 습관은 롤백·감사 추적이 어려워지므로 피하는 편이 좋다.
스키마 파일은 한 파일에 몰아넣기보다 도메인별로 쪼개(schema/users.ts, schema/posts.ts)한 뒤 index.ts에서 export하는 패턴이 유지보수에 유리하다. 외래키·인덱스·부분 유니크 제약까지 TypeScript로 표현할 수 있어, “문서와 실DB가 어긋남”을 줄일 수 있다. 다만 PostgreSQL 전용 기능(예: pgEnum, 확장)을 쓰면 드라이버별 API를 익혀야 하므로, 멀티 DB를 지원하는 서비스라면 공통 추상화 범위를 미리 정해두는 것이 좋다.
Drizzle Kit 설정
// drizzle.config.ts (예: PostgreSQL, drizzle-kit 0.20+ 스타일은 프로젝트 문서와 맞출 것)
import { defineConfig } from 'drizzle-kit';
export default defineConfig({
schema: './src/db/schema/**/*.ts',
out: './drizzle',
dialect: 'postgresql',
dbCredentials: {
url: process.env.DATABASE_URL!,
},
});
마이그레이션 생성 및 실행
# 스키마 변경 → SQL 마이그레이션 생성
npx drizzle-kit generate
# 생성된 마이그레이션을 DB에 적용 (마이그레이션 러너 또는 drizzle-kit migrate)
npx drizzle-kit migrate
# 개발 환경에서 스키마만 빠르게 동기화 (주의: 운영에는 비권장)
npx drizzle-kit push
# 스키마 검증
npx drizzle-kit check
# Drizzle Studio (GUI)
npx drizzle-kit studio
명령 이름은 Drizzle Kit 버전에 따라 다를 수 있다. 레포지토리의 package.json과 공식 마이그레이션 문서를 기준으로 맞추면 된다. 팀 표준으로 migrate 스크립트를 고정해 두면 온보딩 비용이 줄어든다.
트랜잭션
db.transaction에 넘기는 콜백의 tx는 db와 동일한 API를 제공한다. 중요한 점은 같은 트랜잭션 안의 모든 읽기·쓰기가 tx를 경유해야 격리가 보장된다는 것이다. 실수로 콜백 밖의 db를 섞으면 데드락·일관성 깨짐이 나기 쉽다.
격리 수준은 드라이버·DB에 따라 db.transaction 옵션으로 넘긴다(예: PostgreSQL isolationLevel: 'serializable'). 결제·재고 같이 경쟁 조건이 큰 도메인에서는 DB 기본값(read committed)만 믿지 말고, 애플리케이션 락·유니크 제약·SELECT FOR UPDATE 등과 함께 설계하는 것이 안전하다. Drizzle이 해결해 주는 것은 “SQL을 타입 있게 쓰게 해 주는 것”이지, 비즈니스 정합성까지 대신 증명해 주지는 않는다.
import { eq } from 'drizzle-orm';
// 일반: 예외가 나가면 자동 롤백
await db.transaction(async (tx) => {
const [user] = await tx.insert(users).values({
name: 'Alice',
email: '[email protected]',
}).returning();
await tx.insert(posts).values({
title: 'First Post',
content: 'Hello World',
authorId: user.id,
});
});
// 명시적 롤백이 필요한 분기
await db.transaction(async (tx) => {
await tx.insert(users).values({
name: 'Bob',
email: '[email protected]',
});
if (/* 비즈니스 조건 */ false) {
return tx.rollback();
}
});
서버리스·짧은 연결 풀 환경에서는 트랜잭션이 오래 열릴수록 커넥션 고갈 위험이 커진다. 트랜잭션 안에서 외부 HTTP 호출·느린 작업을 넣지 않는 것이 기본 수칙이다.
타입 안정성을 실제로 활용하기
Drizzle의 InferSelectModel, InferInsertModel는 스키마 한 곳이 진실이 되게 해 준다. DTO·API 응답 타입을 수동으로 중복 정의하다 틀어지는 문제를 줄일 수 있다.
import { type InferSelectModel, type InferInsertModel } from 'drizzle-orm';
type User = InferSelectModel<typeof users>;
type NewUser = InferInsertModel<typeof users>;
function toPublicUser(u: User) {
return { id: u.id, name: u.name }; // 민감 필드 제외 시 pick/omit과 조합
}
$inferSelect 같은 테이블 보조 타입(버전에 따라 naming이 다를 수 있음)을 쓰면 select 결과의 컬럼 서브셋까지 좁힐 수 있어, as any을 줄이는 데 도움이 된다. 다만 동적 sql 조각이 많아지면 추론이 끊기므로, 복잡한 리포트 쿼리는 Zod로 런타임 검증을 병행하는 팀이 많다. 타입이 전부를 대신하지는 않는다는 점을 짚고 넘어가자.
성능: prepared statement와 커넥션 풀
Drizzle 자체는 가볍지만, 병목은 항상 DB와 풀 쪽에 있다. postgres.js·node-postgres 등 어댑터는 prepared statement·파이프라이닝 옵션을 드라이버 문서대로 켤 수 있다. 같은 형태의 쿼리를 반복하는 핫 패스(세션 조회, 권한 체크)에서는 prepared statement가 유리한 경우가 많다.
import { eq } from 'drizzle-orm';
import { drizzle } from 'drizzle-orm/postgres-js';
import postgres from 'postgres';
import * as schema from './schema';
const client = postgres(process.env.DATABASE_URL!, { max: 10 }); // 풀 크기
export const db = drizzle(client, { schema });
// 자주 쓰는 쿼리를 함수로 캡슐화 → 실행 계획 캐시와 궁합
export async function getUserById(id: number) {
return db.select().from(schema.users).where(eq(schema.users.id, id)).limit(1);
}
커넥션 풀은 “서버리스 함수마다 풀을 새로” 만들면 오히려 역효과가 날 수 있다. Vercel·Lambda에서는 퍼 런타임 풀 크기와 최대 동시 실행의 균형을 잡고, 가능하면 Supabase/Neon 같은 풀러 프록시를 쓰는 선택지도 있다. Edge(D1)는 모델이 달라서, Node에서의 풀 튜닝과 동일한 가정을 하면 안 된다.
Drizzle Studio (GUI)
# Drizzle Studio 실행
npx drizzle-kit studio
# http://localhost:4983 접속
기능:
- ✅ 데이터베이스 브라우징
- ✅ CRUD 작업
- ✅ SQL 쿼리 실행
- ✅ 스키마 시각화
Edge Runtime 지원 (Cloudflare D1, Turso, Vercel)
Edge에서 Drizzle이 자주 언급되는 이유는 런타임이 제한된 환경에서도 “그냥 import 해서” 쓸 수 있기 때문이다. Prisma는 엔진·바이너리 스토리 때문에 Workers 배포에 부담이 있었고(생태계가 빠르게 변하므로 최신 지침은 Prisma 쪽 문서를 확인), Drizzle·Kysely 계열이 “SQL + TS 타입” 쪽 대안으로 자리 잡았다.
Cloudflare D1
D1은 SQLite 호환 API를 Workers에 붙인 서비스다. Drizzle은 drizzle-orm/d1 엔트리로 D1을 감싼다. 주의할 점은 (1) SQLite 문법·제약, (2) D1의 지연 읽기 일관성·한계, (3) 마이그레이션을 로컬 wrangler로 돌릴지 CI에서 돌릴지 운영 설계다. D1은 “소규모·에지 쪽 캐시·설정”에는 강하고, 복잡한 트랜잭션·고부하 OLTP를 그대로 올리기엔 Node+관리형 Postgres와 트레이드오프가 있다.
// worker.ts
import { drizzle } from 'drizzle-orm/d1';
import * as schema from './schema';
interface Env {
DB: D1Database;
}
export default {
async fetch(request: Request, env: Env): Promise<Response> {
const db = drizzle(env.DB, { schema });
const users = await db.select().from(schema.users);
return Response.json(users);
},
};
Turso (LibSQL)와 @libsql/client
Turso는 LibSQL을 기반으로 한 원격·복제형 SQLite다. 드라이버로 @libsql/client를 쓰고, Drizzle의 drizzle-orm/libsql로 연결하는 패턴이 흔하다. 로컬 개발·테스트는 임베디드, 스테이징/프로덕션은 URL로 붙이는 흐름이 자연스럽다. 다만 “Postgres에 익숙한 팀”이 SQLite로 내려올 때 동시 쓰기·잠금·제약 차이를 과소평가하기 쉬우니, 스키마 설계를 처음부터 Edge용으로 가져갈지 결정해야 한다.
import { drizzle } from 'drizzle-orm/libsql';
import { createClient } from '@libsql/client';
import * as schema from './schema';
const client = createClient({ url: process.env.TURSO_URL!, authToken: process.env.TURSO_AUTH_TOKEN });
export const db = drizzle(client, { schema });
Vercel Edge Functions (Postgres)
Vercel Postgres와 @vercel/postgres를 쓰는 경우, Edge 런타임에서 drizzle-orm/vercel-postgres로 연결할 수 있다. DB가 Edge에 “가깝다”는 것과 “Edge 함수가 늘 그렇다”는 것은 별개이므로, 쿼리 지연·풀·콜드 스타트를 실측하는 것이 중요하다.
// app/api/users/route.ts
import { drizzle } from 'drizzle-orm/vercel-postgres';
import { sql } from '@vercel/postgres';
import * as schema from '@/db/schema';
export const runtime = 'edge';
const db = drizzle(sql, { schema });
export async function GET() {
const users = await db.select().from(schema.users);
return Response.json(users);
}
Prisma·TypeORM과의 비교 (솔직한 평가)
| 축 | Drizzle | Prisma | TypeORM |
|---|---|---|---|
| SQL 가시성 | 높음 (빌더가 SQL에 가깝다) | 중간 (런타임·쿼리 계획이 숨는 편) | 낮~중 (Repository·QB 추상) |
| 온보딩·DX | SQL 친화 팀에 유리 | 문서·스튜디오·생태계가 두껍다 | 데코레이터·NestJS와 궁합 |
| 번들/Edge | 가벼움, Edge 사례 많음 | 과거·현재 러너에 따라 제약 확인 필요 | Node 중심, Edge는 부담 |
| 마이그레이션 | Drizzle Kit, SQL 파일 중심 | Prisma Migrate가 성숙 | typeorm migration / cli |
| 리팩터링 | 스키마 TS가 진실 | schema.prisma + client 생성 | 엔티티 데코레이터 + 메타데이터 |
Prisma는 prisma generate· relation API·PrismaClient의 일관된 스토리가 강점이다. 대규모 팀에서 “DB 담당·백엔드·프론트”가 Prisma에 모두 익숙하다면 속도가 난다. 반면 복잡한 raw SQL이 잦고 Edge 배포가 필수라면 Drizzle 쪽 설득력이 올라간다.
TypeORM은 NestJS와 함께 쓰는 레거시·엔터프라이즈 코드가 많다. ActiveRecord/Repository 패턴에 익숙하면 익숙지지만, 타입이 끊기기 쉬운 부분과 런타임 데코레이터 의존이 부담인 팀도 있다. “점진적 교체”를 하려면 새 모듈만 Drizzle로 쓰는 식이 현실적이다.
Drizzle을 만능으로 말하고 싶지는 않다. 관계 로딩·시드·백오피스 툴까지 한 번에 해결하려는 팀은 Prisma가 여전히 편할 수 있다. Drizzle의 이점은 쿼리가 길어져도 “그게 SQL”이라 팀이 같이 읽을 수 있다는 점, 그리고 Edge·SQLite·LibSQL과의 조합에 있다.
실제 프로덕션에서 느낀 점
필자의 경험상 Drizzle로 이전(또는 신규)한 프로젝트에서는 다음이 반복됐다.
- 초기에는 생산성이 Prisma만큼 “매직”하지 않다.
db.query로 관계를 끌어오는 경로와, 직접select+join을 쓰는 경로가 공존하면 코딩 스타일 가이드가 없을 때 팀마다 쿼리 스타일이 갈라진다. - 성능 이슈는 대부분 인덱스·N+1·풀에서 났다. Drizzle이 느린 것이 아니라,
findMany에 가까운 패턴을 남용했을 때다.EXPLAIN을 붙이기 쉬운 것이 장점이었다. - Drizzle Kit 업그레이드로 CLI 이름이 바뀐 적이 있어,
package.json스크립트에 의존 버전을 고정하는 것이 정신 건강에 이롭다. - Edge+D1 조합은 “API 한두 개”에는 좋고, 강한 일관성이 필요한 결제는 상위 계층에서 별도 설계(아웃박스, 메시지, 단일 writer DB)를 같이 쓰는 경우가 많았다.
즉, 도구는 정직하고, 운영 난이도는 도메인이 결정한다. Drizzle은 SQL을 숨기지 않으므로, SQL에 자신 없는 팀에는 오히려 비용이 될 수 있다. 반대로 SQL·인덱스·실행 계획을 이야기하는 문화라면, 타입과의 결합이 큰 이득이 된다.
실전 프로젝트: 블로그 API
스키마 정의
// src/db/schema.ts
export const users = pgTable('users', {
id: serial('id').primaryKey(),
username: text('username').notNull().unique(),
email: text('email').notNull().unique(),
passwordHash: text('password_hash').notNull(),
createdAt: timestamp('created_at').defaultNow(),
});
export const posts = pgTable('posts', {
id: serial('id').primaryKey(),
title: text('title').notNull(),
slug: text('slug').notNull().unique(),
content: text('content').notNull(),
published: boolean('published').default(false),
authorId: integer('author_id')
.notNull()
.references(() => users.id),
createdAt: timestamp('created_at').defaultNow(),
updatedAt: timestamp('updated_at').defaultNow(),
});
export const comments = pgTable('comments', {
id: serial('id').primaryKey(),
content: text('content').notNull(),
postId: integer('post_id')
.notNull()
.references(() => posts.id, { onDelete: 'cascade' }),
userId: integer('user_id')
.notNull()
.references(() => users.id),
createdAt: timestamp('created_at').defaultNow(),
});
API 라우트
// app/api/posts/route.ts
import { db } from '@/db';
import { posts, users } from '@/db/schema';
import { eq } from 'drizzle-orm';
// GET /api/posts
export async function GET() {
const allPosts = await db
.select({
id: posts.id,
title: posts.title,
slug: posts.slug,
author: users.username,
createdAt: posts.createdAt,
})
.from(posts)
.innerJoin(users, eq(posts.authorId, users.id))
.where(eq(posts.published, true))
.orderBy(posts.createdAt);
return Response.json(allPosts);
}
// POST /api/posts
export async function POST(request: Request) {
const body = await request.json();
const [post] = await db.insert(posts).values({
title: body.title,
slug: body.slug,
content: body.content,
authorId: body.authorId,
}).returning();
return Response.json(post, { status: 201 });
}
핵심 정리
✅ Drizzle ORM의 장점
- SQL과 유사한 문법: 배우기 쉬움
- 가볍고 빠름: Prisma의 1/10 크기, 2배 성능
- Edge Runtime 지원: Cloudflare Workers·Vercel Edge
- 완벽한 타입 추론: TypeScript 타입 안전성
- Drizzle Studio: 강력한 GUI 도구
🚀 다음 단계
- Drizzle 공식 문서에서 심화 학습
- Drizzle GitHub에서 소스 코드 탐색
- Discord에서 커뮤니티 참여
시작하기:
npm install drizzle-orm으로 5분 만에 프로젝트를 시작하고, SQL의 단순함과 TypeScript의 타입 안전성을 동시에 누리세요! 🚀