타입 안전한 ORM — Prisma, Drizzle의 타입 생성 원리
타입 안전한 ORM은 데이터베이스 스키마에서 TypeScript 타입을 자동 생성 해서, 쿼리의 입력과 출력을 컴파일 타임에 검증합니다.
Prisma의 타입 생성
스키마 정의
// schema.prisma
model User {
id Int @id @default(autoincrement())
name String
email String @unique
posts Post[]
createdAt DateTime @default(now())
}
model Post {
id Int @id @default(autoincrement())
title String
content String?
published Boolean @default(false)
author User @relation(fields: [authorId], references: [id])
authorId Int
}
코드 생성 (Code Generation)
npx prisma generate
# → node_modules/.prisma/client/index.d.ts에 타입 생성
생성되는 타입:
// 자동 생성된 타입 (간략화)
interface User {
id: number;
name: string;
email: string;
createdAt: Date;
}
interface Post {
id: number;
title: string;
content: string | null;
published: boolean;
authorId: number;
}
타입 안전한 쿼리
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
// ✅ 타입 안전한 CRUD
const user = await prisma.user.create({
data: {
name: '홍길동',
email: 'hong@test.com',
// id: 1, // ❌ Error — autoincrement이므로 전달 불가
},
});
// user: User — 반환 타입이 자동 추론
// ✅ 관계 포함 쿼리
const userWithPosts = await prisma.user.findUnique({
where: { id: 1 },
include: { posts: true },
});
// userWithPosts: User & { posts: Post[] } | null
// ✅ select로 필드 선택
const partial = await prisma.user.findMany({
select: { name: true, email: true },
});
// partial: { name: string; email: string }[]
// ❌ 존재하지 않는 필드
// prisma.user.findMany({ where: { phone: '010' } }); // Error
Prisma의 고급 타입 활용
import { Prisma } from '@prisma/client';
// 모델의 입력 타입
type UserCreateInput = Prisma.UserCreateInput;
// { name: string; email: string; posts?: ... }
// 모델의 where 조건 타입
type UserWhereInput = Prisma.UserWhereInput;
// { id?: number | IntFilter; name?: string | StringFilter; ... }
// select/include에 따른 반환 타입 계산
type UserWithPosts = Prisma.UserGetPayload<{
include: { posts: true };
}>;
// User & { posts: Post[] }
Drizzle의 타입 추론
Drizzle은 코드 생성 없이 TypeScript 추론만으로 타입을 제공합니다.
스키마 정의
import { pgTable, serial, text, boolean, integer, timestamp } from 'drizzle-orm/pg-core';
// 스키마가 TypeScript 코드
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'),
published: boolean('published').default(false),
authorId: integer('author_id').notNull().references(() => users.id),
});
// 타입 추출
type User = typeof users.$inferSelect;
// { id: number; name: string; email: string; createdAt: Date | null }
type NewUser = typeof users.$inferInsert;
// { name: string; email: string; id?: number; createdAt?: Date }
타입 안전한 쿼리
import { eq, and, gt } from 'drizzle-orm';
// 타입 안전한 쿼리
const allUsers = await db.select().from(users);
// User[]
// 조건부 쿼리
const activeUsers = await db
.select({ name: users.name, email: users.email })
.from(users)
.where(eq(users.name, '홍길동'));
// { name: string; email: string }[]
// 조인
const postsWithAuthor = await db
.select()
.from(posts)
.leftJoin(users, eq(posts.authorId, users.id));
// { posts: Post; users: User | null }[]
Prisma vs Drizzle 비교
| 특성 | Prisma | Drizzle |
|---|---|---|
| 타입 생성 | 코드 제너레이터 | TypeScript 추론 |
| 스키마 언어 | .prisma (독자 DSL) | TypeScript |
| 빌드 단계 | prisma generate 필요 | 불필요 |
| 쿼리 스타일 | 객체 기반 | SQL 유사 |
| 번들 크기 | 크다 | 작다 |
| 관계 쿼리 | include 옵션 | 명시적 조인 |
타입 생성의 내부 원리
Prisma: 코드 생성
schema.prisma → prisma generate → .d.ts 파일 생성 → TypeScript가 읽음
Prisma는 스키마 파일을 파싱해서 TypeScript 타입 선언 파일을 ** 직접 생성 **합니다. 모든 가능한 쿼리 조합에 대한 타입이 미리 생성됩니다.
Drizzle: 타입 추론
TypeScript 스키마 코드 → 제네릭 + 조건부 타입 → TypeScript 컴파일러가 추론
Drizzle은 스키마 정의가 TypeScript 코드이므로 제네릭과 조건부 타입으로 쿼리 결과의 타입을 ** 실시간으로 추론 **합니다.
정리
- Prisma는
.prisma스키마에서 TypeScript 타입을 코드 생성한다 - Drizzle은 스키마가 TypeScript이므로 추론만으로 타입을 제공한다
- 두 ORM 모두 쿼리의 입력/출력을 컴파일 타임에 검증한다
select,include에 따라 반환 타입이 자동으로 달라진다- Prisma는 편의성, Drizzle은 유연성과 경량 번들이 강점이다
댓글 로딩 중...