10 KiB
10 KiB
Drizzle ORM 사용 가이드
✅ Drizzle ORM으로 마이그레이션 완료
기존 순수 SQL 쿼리 방식에서 Drizzle ORM으로 완전히 전환되었습니다.
🎯 Drizzle ORM의 장점
- 타입 안전성: TypeScript와 완벽한 통합
- 자동 완성: IDE에서 테이블과 컬럼 자동 완성
- 마이그레이션 관리: 스키마 변경을 자동으로 SQL로 생성
- 쿼리 빌더: SQL 인젝션 방지 및 가독성 향상
- Cloudflare D1 최적화: D1과 완벽하게 호환
📦 설치된 패키지
{
"dependencies": {
"drizzle-orm": "^0.44.7"
},
"devDependencies": {
"drizzle-kit": "^0.31.7"
}
}
📁 파일 구조
프로젝트/
├── drizzle.config.ts # Drizzle 설정
├── drizzle/ # 마이그레이션 파일
│ └── 0000_*.sql # 생성된 SQL 마이그레이션
└── src/
└── lib/
└── server/
├── schema.ts # Drizzle 스키마 정의
└── db.ts # 데이터베이스 함수 (Drizzle 사용)
🔧 스키마 정의 (schema.ts)
import { sqliteTable, text, integer } from 'drizzle-orm/sqlite-core';
import { sql } from 'drizzle-orm';
export const users = sqliteTable('users', {
id: integer('id').primaryKey({ autoIncrement: true }),
email: text('email').notNull().unique(),
passwordHash: text('password_hash').notNull(),
nickname: text('nickname').notNull(),
createdAt: integer('created_at', { mode: 'timestamp' })
.notNull()
.default(sql`(strftime('%s', 'now'))`)
});
export type User = typeof users.$inferSelect;
export type NewUser = typeof users.$inferInsert;
타입 추론
User: SELECT 쿼리 결과 타입 (모든 필드 포함)NewUser: INSERT 시 사용하는 타입 (자동 생성 필드 제외)
💾 데이터베이스 함수 (db.ts)
기존 (순수 SQL)
// ❌ 타입 안전성 없음, 오타 발생 가능
const user = await db
.prepare('SELECT id, email, password_hash, nickname FROM users WHERE email = ?')
.bind(email)
.first<User>();
Drizzle 사용 (현재)
// ✅ 완전한 타입 안전성
import { eq } from 'drizzle-orm';
const [user] = await drizzleDb
.select()
.from(users)
.where(eq(users.email, email))
.limit(1);
전체 함수 예시
import { drizzle } from 'drizzle-orm/d1';
import { eq } from 'drizzle-orm';
import { users, type User, type NewUser } from './schema';
export function getDb(d1: D1Database) {
return drizzle(d1);
}
// CREATE
export async function createUser(
db: D1Database,
email: string,
passwordHash: string,
nickname: string
): Promise<User | null> {
const drizzleDb = getDb(db);
await drizzleDb.insert(users).values({
email,
passwordHash,
nickname
});
const [user] = await drizzleDb
.select()
.from(users)
.where(eq(users.email, email))
.limit(1);
return user || null;
}
// READ
export async function getUserByEmail(db: D1Database, email: string) {
const drizzleDb = getDb(db);
const [user] = await drizzleDb
.select()
.from(users)
.where(eq(users.email, email))
.limit(1);
return user || null;
}
// UPDATE
export async function updateUserNickname(
db: D1Database,
userId: number,
nickname: string
) {
const drizzleDb = getDb(db);
await drizzleDb
.update(users)
.set({ nickname })
.where(eq(users.id, userId));
}
// DELETE
export async function deleteUser(db: D1Database, userId: number) {
const drizzleDb = getDb(db);
await drizzleDb
.delete(users)
.where(eq(users.id, userId));
}
📜 Package.json 스크립트
{
"scripts": {
"db:generate": "drizzle-kit generate",
"db:push": "wrangler d1 execute auth-db --local --file=./drizzle/0000_*.sql",
"db:push:remote": "wrangler d1 execute auth-db --remote --file=./drizzle/0000_*.sql",
"db:studio": "drizzle-kit studio",
"db:query": "wrangler d1 execute auth-db --local --command=\"SELECT * FROM users\"",
"db:query:remote": "wrangler d1 execute auth-db --remote --command=\"SELECT * FROM users\""
}
}
명령어 설명
pnpm db:generate
스키마 파일(schema.ts)에서 SQL 마이그레이션 파일을 자동 생성합니다.
pnpm db:generate
# 출력: drizzle/0000_omniscient_lady_mastermind.sql 생성
pnpm db:push
생성된 마이그레이션을 로컬 D1 데이터베이스에 적용합니다.
pnpm db:push
# 로컬 SQLite DB에 테이블 생성
pnpm db:push:remote
프로덕션 D1 데이터베이스에 마이그레이션을 적용합니다.
pnpm db:push:remote
# Cloudflare의 원격 D1에 테이블 생성
pnpm db:studio
Drizzle Studio를 실행하여 브라우저에서 데이터베이스를 시각적으로 관리합니다.
pnpm db:studio
# https://local.drizzle.studio 에서 DB 관리
pnpm db:query
로컬 데이터베이스에서 쿼리를 실행합니다.
pnpm db:query
# users 테이블의 모든 데이터 조회
🚀 개발 워크플로우
1. 스키마 변경
새로운 테이블이나 컬럼을 추가할 때:
// src/lib/server/schema.ts
export const users = sqliteTable('users', {
id: integer('id').primaryKey({ autoIncrement: true }),
email: text('email').notNull().unique(),
passwordHash: text('password_hash').notNull(),
nickname: text('nickname').notNull(),
createdAt: integer('created_at', { mode: 'timestamp' })
.notNull()
.default(sql`(strftime('%s', 'now'))`),
// 새로운 컬럼 추가
avatar: text('avatar')
});
2. 마이그레이션 생성
pnpm db:generate
이 명령어는 스키마 변경사항을 분석하여 SQL 마이그레이션 파일을 생성합니다:
-- drizzle/0001_new_migration.sql
ALTER TABLE users ADD COLUMN avatar text;
3. 로컬 DB에 적용
# 파일명을 실제 생성된 파일명으로 변경
pnpm wrangler d1 execute auth-db --local --file=./drizzle/0001_new_migration.sql
4. 코드에서 사용
// 타입이 자동으로 업데이트됨
const [user] = await drizzleDb
.select()
.from(users)
.where(eq(users.email, email))
.limit(1);
// user.avatar는 타입 체크됨!
console.log(user.avatar);
🔍 Drizzle Studio 사용법
Drizzle Studio는 웹 기반 데이터베이스 관리 도구입니다.
pnpm db:studio
실행 후 브라우저가 자동으로 열리며 다음 기능을 사용할 수 있습니다:
- 📊 테이블 데이터 조회 및 편집
- ➕ 새 레코드 추가
- 🗑️ 레코드 삭제
- 🔍 검색 및 필터링
- 📈 관계 시각화
🎯 고급 쿼리 예제
여러 조건으로 검색
import { and, eq, like } from 'drizzle-orm';
const users = await drizzleDb
.select()
.from(users)
.where(
and(
like(users.nickname, '%테스트%'),
eq(users.email, 'test@example.com')
)
);
정렬 및 페이지네이션
import { desc } from 'drizzle-orm';
const users = await drizzleDb
.select()
.from(users)
.orderBy(desc(users.createdAt))
.limit(10)
.offset(0);
JOIN 쿼리 (추후 테이블 추가 시)
// 예: posts 테이블이 있다면
const postsWithUsers = await drizzleDb
.select()
.from(posts)
.leftJoin(users, eq(posts.userId, users.id));
집계 함수
import { count } from 'drizzle-orm';
const [result] = await drizzleDb
.select({ count: count() })
.from(users);
console.log(`총 사용자 수: ${result.count}`);
🔐 타입 안전성의 이점
Before (순수 SQL)
// ❌ 오타 발생 가능
const user = await db.prepare('SELECT * FROM usres WHERE email = ?');
// ^^^^^ 오타!
// ❌ 컬럼명 오타
const user = await db.prepare('SELECT emial FROM users');
// ^^^^^ 오타!
// ❌ 런타임에서만 에러 발견
After (Drizzle)
// ✅ 컴파일 타임에 에러 발견
const user = await drizzleDb
.select()
.from(usres) // ← IDE에서 빨간 줄, 컴파일 에러
.where(eq(users.emial, email)); // ← 컴파일 에러
// ✅ 자동 완성 지원
const user = await drizzleDb
.select()
.from(users)
.where(eq(users. /* ← IDE가 email, nickname 등 제안 */
📝 마이그레이션 히스토리 관리
Drizzle은 마이그레이션 파일을 순차적으로 관리합니다:
drizzle/
├── 0000_omniscient_lady_mastermind.sql # 초기 스키마
├── 0001_add_avatar_column.sql # 아바타 컬럼 추가
├── 0002_add_posts_table.sql # 게시글 테이블 추가
└── meta/
└── _journal.json # 마이그레이션 히스토리
각 마이그레이션은 독립적으로 적용 가능하며, Drizzle이 자동으로 순서를 관리합니다.
🆚 순수 SQL vs Drizzle 비교
| 기능 | 순수 SQL | Drizzle ORM |
|---|---|---|
| 타입 안전성 | ❌ 런타임에만 확인 | ✅ 컴파일 타임 확인 |
| 자동 완성 | ❌ 없음 | ✅ 전체 지원 |
| SQL 인젝션 | ⚠️ 수동 방어 필요 | ✅ 자동 방지 |
| 마이그레이션 | 📝 수동 작성 | 🤖 자동 생성 |
| 쿼리 작성 | 문자열 기반 | 타입 안전 빌더 |
| 리팩토링 | ❌ 어려움 | ✅ 쉬움 |
| 학습 곡선 | 낮음 | 중간 |
| 성능 | 빠름 | 빠름 (동일) |
🎓 추가 학습 자료
✨ 다음 단계
Drizzle ORM을 활용하여 다음 기능들을 쉽게 추가할 수 있습니다:
- 게시글 테이블 - 사용자와 1:N 관계
- 댓글 시스템 - 게시글과 1:N 관계
- 좋아요 기능 - M:N 관계
- 팔로우 시스템 - 자기 참조 관계
- 프로필 설정 - 1:1 관계
모든 관계가 타입 안전하게 관리됩니다!
🔥 현재 프로젝트 상태
✅ Drizzle ORM 완전 통합 완료 ✅ 타입 안전한 쿼리 함수 구현 ✅ 마이그레이션 시스템 구축 ✅ 로컬/프로덕션 환경 분리 ✅ 빌드 성공 확인
이제 pnpm cf:dev로 서버를 실행하고 JWT 인증과 Drizzle ORM이 함께 작동하는 것을 확인할 수 있습니다!