01. Drizzle ORM 소개
📋 개요
ORM(Object-Relational Mapping)은 데이터베이스 테이블을 코드의 객체로 매핑하여, SQL을 직접 작성하지 않고도 데이터베이스를 조작할 수 있게 해주는 도구입니다.
📋 목차
ORM이란?
ORM(Object-Relational Mapping)은 데이터베이스 테이블을 코드의 객체로 매핑하여, SQL을 직접 작성하지 않고도 데이터베이스를 조작할 수 있게 해주는 도구입니다.
┌─────────────────────────────────────────────────────────┐
│ TypeScript 코드 ←→ ORM ←→ PostgreSQL │
│ db.select().from(users) → SELECT * FROM users │
└─────────────────────────────────────────────────────────┘
Drizzle ORM 특징
| 특징 | 설명 |
|---|---|
| 타입 안전성 | 스키마에서 TypeScript 타입 자동 추론 |
| SQL-like 문법 | SQL과 유사한 API로 학습 곡선 낮음 |
| 경량 | 런타임 의존성 최소화, 빠른 실행 속도 |
| Serverless 최적화 | Edge 환경에서도 동작 |
프로젝트 구조
packages/schema/ # 공유 스키마 패키지
├── src/
│ ├── schema/ # 테이블 정의
│ │ ├── users.ts # 사용자 테이블
│ │ ├── stocks.ts # 주식 테이블
│ │ └── index.ts # 스키마 내보내기
│ └── index.ts # 메인 진입점
└── drizzle.config.ts # Drizzle Kit 설정
구조 설명:
packages/schema/: 모노레포에서 FE/BE가 공유하는 스키마 패키지입니다.schema/: 각 도메인별 테이블 정의를 분리하여 관리합니다.drizzle.config.ts: 마이그레이션, DB 연결 등 Drizzle Kit 도구 설정 파일입니다.
데이터베이스 연결
// packages/schema/src/db.ts
import { drizzle } from 'drizzle-orm/postgres-js';
import postgres from 'postgres';
import * as schema from './schema';
// 1. PostgreSQL 클라이언트 생성
const client = postgres(process.env.DATABASE_URL!, {
max: 10, // 최대 연결 수
idle_timeout: 20, // 유휴 연결 타임아웃 (초)
connect_timeout: 10, // 연결 타임아웃 (초)
});
// 2. Drizzle 인스턴스 생성
export const db = drizzle(client, { schema });코드 설명:
postgres(DATABASE_URL):postgres라이브러리로 PostgreSQL에 연결합니다. 연결 풀 설정도 여기서 지정합니다.max: 10: 동시에 유지할 수 있는 최대 DB 연결 수입니다. 서버 부하에 따라 조절합니다.drizzle(client, { schema }): Drizzle 인스턴스를 생성합니다.schema를 전달하면 관계형 쿼리 API를 사용할 수 있습니다.export const db: 이 인스턴스를 애플리케이션 전체에서 임포트하여 사용합니다.
첫 번째 스키마 정의
// packages/schema/src/schema/users.ts
import { pgTable, uuid, varchar, timestamp } from 'drizzle-orm/pg-core';
export const users = pgTable('users', {
// uuid 타입의 기본키, 자동 생성
id: uuid('id').primaryKey().defaultRandom(),
// varchar(100) 타입, null 불가, 중복 불가
email: varchar('email', { length: 100 }).notNull().unique(),
// varchar(50) 타입, null 불가
name: varchar('name', { length: 50 }).notNull(),
// 생성 시간, 기본값: 현재 시간
createdAt: timestamp('created_at').defaultNow().notNull(),
// 수정 시간, 레코드 변경 시 자동 업데이트
updatedAt: timestamp('updated_at').defaultNow().notNull(),
});각 컬럼 설명:
uuid('id'): PostgreSQL의 UUID 타입입니다.defaultRandom()은 자동으로 고유 ID를 생성합니다.varchar('email', { length: 100 }): 최대 100자 문자열입니다.unique()로 중복 이메일을 방지합니다.timestamp('created_at'): 날짜/시간 타입입니다.defaultNow()는 INSERT 시 현재 시간을 자동 설정합니다..notNull(): NULL 값을 허용하지 않는 제약 조건입니다.
기본 조회 예제
import { db } from './db';
import { users } from './schema';
import { eq } from 'drizzle-orm';
// 모든 사용자 조회
const allUsers = await db.select().from(users);
// 특정 사용자 조회
const user = await db
.select()
.from(users)
.where(eq(users.id, 'user-uuid-here'));코드 설명:
db.select().from(users):SELECT * FROM users와 동일합니다. 모든 컬럼을 가져옵니다.eq(users.id, 'user-uuid-here'):WHERE id = 'user-uuid-here'조건입니다.eq는 equality(동등) 비교 연산자입니다.where(): SQL의 WHERE 절에 해당하며, 여러 조건을 체이닝할 수 있습니다.
레퍼런스
다음 장: 02. 스키마 기초
📝 마무리 퀴즈
Q1. Drizzle 인스턴스를 만들 때 drizzle(client, { schema })처럼 schema를 함께 넘기는 이유는 무엇인가요?
✅ 정답: 관계형 쿼리 API에서 테이블과 relations 타입을 추론하게 만들기 위해서입니다.
💡 상세 해설: schema를 넘기면 db.query.users.findMany() 같은 API에서 컬럼, 관계, 반환 타입을 TypeScript가 따라옵니다. 단순히 SQL 문자열을 줄이는 것이 아니라, DB 설계와 애플리케이션 코드 사이의 계약을 타입으로 드러내는 것이 Drizzle의 핵심입니다.
Q2. 영수네 커뮤니티가 서버리스 환경에서 Drizzle을 쓰려 할 때, DB 연결 코드에서 가장 먼저 점검할 운영 리스크는 무엇인가요?
✅ 정답: 연결 수와 타임아웃을 환경에 맞게 제한하는 것입니다.
💡 상세 해설: 예제의 max, idle_timeout, connect_timeout은 장애 방지 장치입니다. 서버리스나 여러 인스턴스 환경에서 연결을 무제한으로 열면 DB 커넥션이 고갈될 수 있습니다.
Q3. 영철이의 테스트 타임: users.email에 .unique()를 빼고 API 레벨에서만 중복 검사를 하기로 했습니다. 무엇이 위험할까요?
✅ 정답: 동시 요청에서 중복 이메일이 들어갈 수 있으므로 DB 제약조건으로도 막아야 합니다.
💡 상세 해설: API에서 먼저 조회하고 없으면 INSERT하는 방식은 두 요청이 동시에 들어오면 둘 다 "없다"고 판단할 수 있습니다. .unique()는 애플리케이션 버그나 경쟁 상태가 있어도 DB가 마지막 방어선을 잡아주는 장치입니다.
🐣 영철이의 퇴근 일기
오늘은 Drizzle을 "SQL을 덜 쓰는 도구" 정도로만 보면 안 된다는 걸 배웠다. pgTable 하나가 타입, 쿼리, 마이그레이션의 출발점이 되고, schema를 DB 인스턴스에 넘기는 순간 코드가 DB 구조를 따라 움직이기 시작했다.
영수 님이 "가입 이메일 중복되면 운영팀이 수동으로 합쳐야 한다"고 말했을 때, 유니크 제약조건이 왜 단순 옵션이 아닌지 확 와닿았다.
💡 "ORM을 고를 때는 문법보다 계약을 본다. 스키마, 타입, DB 제약조건이 같은 방향을 보고 있어야 운영이 편해진다."
내일 코드 리뷰에서는 DATABASE_URL만 확인하지 말고, 커넥션 수와 스키마 전달 여부, DB 제약조건까지 같이 확인해야겠다.