# Drizzle ORM 사용 가이드 ## ✅ Drizzle ORM으로 마이그레이션 완료 기존 순수 SQL 쿼리 방식에서 Drizzle ORM으로 완전히 전환되었습니다. ## 🎯 Drizzle ORM의 장점 - **타입 안전성**: TypeScript와 완벽한 통합 - **자동 완성**: IDE에서 테이블과 컬럼 자동 완성 - **마이그레이션 관리**: 스키마 변경을 자동으로 SQL로 생성 - **쿼리 빌더**: SQL 인젝션 방지 및 가독성 향상 - **Cloudflare D1 최적화**: D1과 완벽하게 호환 ## 📦 설치된 패키지 ```json { "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) ```typescript 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) ```typescript // ❌ 타입 안전성 없음, 오타 발생 가능 const user = await db .prepare('SELECT id, email, password_hash, nickname FROM users WHERE email = ?') .bind(email) .first(); ``` ### Drizzle 사용 (현재) ```typescript // ✅ 완전한 타입 안전성 import { eq } from 'drizzle-orm'; const [user] = await drizzleDb .select() .from(users) .where(eq(users.email, email)) .limit(1); ``` ### 전체 함수 예시 ```typescript 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 { 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 스크립트 ```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 마이그레이션 파일을 자동 생성합니다. ```bash pnpm db:generate # 출력: drizzle/0000_omniscient_lady_mastermind.sql 생성 ``` #### `pnpm db:push` 생성된 마이그레이션을 로컬 D1 데이터베이스에 적용합니다. ```bash pnpm db:push # 로컬 SQLite DB에 테이블 생성 ``` #### `pnpm db:push:remote` 프로덕션 D1 데이터베이스에 마이그레이션을 적용합니다. ```bash pnpm db:push:remote # Cloudflare의 원격 D1에 테이블 생성 ``` #### `pnpm db:studio` Drizzle Studio를 실행하여 브라우저에서 데이터베이스를 시각적으로 관리합니다. ```bash pnpm db:studio # https://local.drizzle.studio 에서 DB 관리 ``` #### `pnpm db:query` 로컬 데이터베이스에서 쿼리를 실행합니다. ```bash pnpm db:query # users 테이블의 모든 데이터 조회 ``` ## 🚀 개발 워크플로우 ### 1. 스키마 변경 새로운 테이블이나 컬럼을 추가할 때: ```typescript // 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. 마이그레이션 생성 ```bash pnpm db:generate ``` 이 명령어는 스키마 변경사항을 분석하여 SQL 마이그레이션 파일을 생성합니다: ```sql -- drizzle/0001_new_migration.sql ALTER TABLE users ADD COLUMN avatar text; ``` ### 3. 로컬 DB에 적용 ```bash # 파일명을 실제 생성된 파일명으로 변경 pnpm wrangler d1 execute auth-db --local --file=./drizzle/0001_new_migration.sql ``` ### 4. 코드에서 사용 ```typescript // 타입이 자동으로 업데이트됨 const [user] = await drizzleDb .select() .from(users) .where(eq(users.email, email)) .limit(1); // user.avatar는 타입 체크됨! console.log(user.avatar); ``` ## 🔍 Drizzle Studio 사용법 Drizzle Studio는 웹 기반 데이터베이스 관리 도구입니다. ```bash pnpm db:studio ``` 실행 후 브라우저가 자동으로 열리며 다음 기능을 사용할 수 있습니다: - 📊 테이블 데이터 조회 및 편집 - ➕ 새 레코드 추가 - 🗑️ 레코드 삭제 - 🔍 검색 및 필터링 - 📈 관계 시각화 ## 🎯 고급 쿼리 예제 ### 여러 조건으로 검색 ```typescript 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') ) ); ``` ### 정렬 및 페이지네이션 ```typescript import { desc } from 'drizzle-orm'; const users = await drizzleDb .select() .from(users) .orderBy(desc(users.createdAt)) .limit(10) .offset(0); ``` ### JOIN 쿼리 (추후 테이블 추가 시) ```typescript // 예: posts 테이블이 있다면 const postsWithUsers = await drizzleDb .select() .from(posts) .leftJoin(users, eq(posts.userId, users.id)); ``` ### 집계 함수 ```typescript import { count } from 'drizzle-orm'; const [result] = await drizzleDb .select({ count: count() }) .from(users); console.log(`총 사용자 수: ${result.count}`); ``` ## 🔐 타입 안전성의 이점 ### Before (순수 SQL) ```typescript // ❌ 오타 발생 가능 const user = await db.prepare('SELECT * FROM usres WHERE email = ?'); // ^^^^^ 오타! // ❌ 컬럼명 오타 const user = await db.prepare('SELECT emial FROM users'); // ^^^^^ 오타! // ❌ 런타임에서만 에러 발견 ``` ### After (Drizzle) ```typescript // ✅ 컴파일 타임에 에러 발견 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 공식 문서](https://orm.drizzle.team/) - [Drizzle with Cloudflare D1](https://orm.drizzle.team/docs/get-started-sqlite#cloudflare-d1) - [Drizzle Studio 가이드](https://orm.drizzle.team/drizzle-studio/overview) ## ✨ 다음 단계 Drizzle ORM을 활용하여 다음 기능들을 쉽게 추가할 수 있습니다: 1. **게시글 테이블** - 사용자와 1:N 관계 2. **댓글 시스템** - 게시글과 1:N 관계 3. **좋아요 기능** - M:N 관계 4. **팔로우 시스템** - 자기 참조 관계 5. **프로필 설정** - 1:1 관계 모든 관계가 타입 안전하게 관리됩니다! ## 🔥 현재 프로젝트 상태 ✅ Drizzle ORM 완전 통합 완료 ✅ 타입 안전한 쿼리 함수 구현 ✅ 마이그레이션 시스템 구축 ✅ 로컬/프로덕션 환경 분리 ✅ 빌드 성공 확인 이제 `pnpm cf:dev`로 서버를 실행하고 JWT 인증과 Drizzle ORM이 함께 작동하는 것을 확인할 수 있습니다!