이미지 최적화를 공부하면서 인프라를 공부해야겠다는 다짐을 했다.
하지만 단순히 인프라를 공부하는 것 보단 실습을 하면서 체득하는 것이 더 좋을 것 같아.
간단한 사이드 프로젝트를 만들기로 다짐했다. 이미지를 최대한 많이 다룰 수 있는 앱을 생각해보니 SNS 앱밖에 떠오르지 않아서..
Next.js 로 SNS 앱을 개발하려고 한다. 동시에 데이터베이스 관리에 Drizzle ORM을 도입했다.
이 글에서는 직접 SQL을 작성하는 방식과 Drizzle을 사용하는 방식을 비교하며 실제로 어떤 문제들을 해결할 수 있었는지 기록해보려고 한다.
ORM이란?
ORM(Object-Relational Mapping)은 객체 지향 프로그래밍 언어와 관계형 데이터베이스 사이의 데이터를 변환해주는 도구이다.
쉽게 말해, SQL을 직접 작성하지 않고 코드로 데이터베이스를 다룰 수 있게 해주는 것 이다.
코드로 비교해보자면 다음과 같다.
만약 ORM 없이 직접 SQL을 작성한다면?
import mysql from 'mysql2/promise';
// 1. DB 연결 생성
const connection = await mysql.createConnection({
host: 'localhost',
user: 'root',
password: 'password',
database: 'mydb'
});
// 2. SQL 쿼리 문자열 직접 작성
const [rows] = await connection.query(
"SELECT * FROM users WHERE email = ?", // SQL 문자열
[email] // 파라미터 배열
);
// 3. 결과가 any 타입 - 타입 안정성 없음
const user = rows[0]; // user: any
console.log(user.email); // 오타 있어도 컴파일 에러 안남
console.log(user.emial); // 런타임에서야 에러가 발견된다.
// 4. 연결 종료 잊으면 메모리 누수
await connection.end();
위의 문제점은 아래와 같다.
1. SQL문을 문자열로 작성했기 때문에 오타가 있어도 실행 전까지는 모른다.
2. 결과값이 any 타입이기 때문에 Typescript의 타입 체크가 무용지물이다.(확인)
3. 매번 연결 생성/ 종료를 관리해줘야 한다. 만약 종료를 잊으면 메모리 누수가 발생한다.
하지만 ORM을 사용한다면? (Drizzle)
import { db } from '@/lib/db';
import { users } from '@/lib/db/schema';
import { eq } from 'drizzle-orm';
// 1. 타입이 자동으로 추론됨
const user = await db.query.users.findFirst({
where: eq(users.email, email)
});
// 2. schema 설정으로 user의 타입이 명확함
// user: { id: string; email: string; name: string; ... } | undefined
// 3. IDE가 속성을 자동완성해줌
console.log(user.email); // 자동완성 지원
console.log(user.emial); // 컴파일 시점에 에러가 발생한다.
// -> error : Property 'emial' does not exist
직접 SQL문을 작성하는것과는 다르게 아래와 같은 장점들이 있다.
1. SQL을 직접 작성하지 않고 사용하는 언어로 데이터베이스에 접근할 수 있다.
2. 객체지향적으로 코드를 작성할 수 있기 때문에 비즈니스 로직에만 집중할 수 있다.
3. 데이터베이스 시스템이 추상화되어 있어 종속성이 낮다.
-> MySQL을 쓰다가 PostgreSQL로 바꿔도 코드 변경이 거의 없습니다. 스키마 정의와 설정 파일만 수정하면 된다.
4. 스키마가 코드로 정의되어 있어서, 별도의 ERD 문서 없이도 데이터 구조를 파악할 수 있다.
5. 자동으로 타입을 추론하여 타입 안정성을 보장한다.
6. 연결 관리가 자동화된다.
하지만 프로젝트의 복잡성이 커질수록 사용 난이도도 올라간다.
간단한 CRUD는 쉽지만, 복잡한 조인이나 서브쿼리가 필요할 때는 ORM 문법이 오히려 더 복잡할 수 있다.
또는 복잡하고 무거운 쿼리는 ORM으로 해결이 불가능한 경우가 있다.
매우 최적화된 성능이 필요하거나, 데이터베이스 특화 기능을 써야 할 때는 Raw SQL을 직접 작성해야 할 수도 있다.
https://orm.drizzle.team/docs/get-started/mysql-new
Drizzle ORM - MySQL
Drizzle ORM is a lightweight and performant TypeScript ORM with developer experience in mind.
orm.drizzle.team
실제로 위 공식문서를 참고해 프로젝트에 Drizzle을 어떻게 적용했는지를 작성해보려고 한다.
우선 기본적인 파일 구조는 아래와 같다.

1단계: 프로젝트 환경 설정
1.1 의존성 설치
# Drizzle ORM과 MySQL 드라이버 설치
npm install drizzle-orm mysql2
# Drizzle Kit (마이그레이션 도구) 설치
npm install -D drizzle-kit
# 환경변수 로딩용 (마이그레이션에 필요)
npm install dotenv
1.2 환경변수 설정
.env.local 파일에 데이터베이스 연결 정보 추가
# .env.local
DB_HOST=your-database-host
DB_PORT=3306
DB_USER=admin
DB_PASSWORD=your-password
DB_NAME=mydb
2단계: Drizzle 설정 파일 생성
프로젝트 루트에 drizzle.config.ts 생성
// drizzle.config.ts
import type { Config } from "drizzle-kit";
import { config } from "dotenv";
// .env.local 파일 로드
config({ path: ".env.local" });
export default {
schema: "./src/lib/db/schema.ts", // 스키마 파일 위치
out: "./drizzle", // 마이그레이션 파일 저장 위치
dialect: "mysql", // 사용할 데이터베이스
dbCredentials: {
host: process.env.DB_HOST!,
port: Number(process.env.DB_PORT),
user: process.env.DB_USER!,
password: process.env.DB_PASSWORD!,
database: process.env.DB_NAME!,
},
} satisfies Config;
공식문서에선 database url 형식으로 아래와 같이 한줄로 작성되어 있다.
DATABASE_URL="mysql://admin:your-password@your-host:3306/mydb"
3단계: 스키마 정의
3.1 User 테이블 스키마
// lib/db/schema.ts
import { mysqlTable, varchar, text, timestamp } from "drizzle-orm/mysql-core";
export const users = mysqlTable("users", {
id: varchar("id", { length: 36 }).primaryKey().$defaultFn(() => crypto.randomUUID()),
email: varchar("email", { length: 255 }).notNull().unique(),
username: varchar("username", { length: 50 }).notNull().unique(),
password: varchar("password", { length: 255 }),
name: varchar("name", { length: 100 }).notNull(),
bio: text("bio"),
profileImage: varchar("profile_image", { length: 500 }),
createdAt: timestamp("created_at").defaultNow().notNull(),
updatedAt: timestamp("updated_at").defaultNow().notNull(),
});
// TypeScript 타입 자동 추론
export type User = typeof users.$inferSelect;
export type NewUser = typeof users.$inferInsert;
3.2 Posts 테이블 추가
같은 파일에 Posts 테이블 추가
import { mysqlTable, varchar, text, timestamp, json } from "drizzle-orm/mysql-core";
// ... users 테이블 정의
export const posts = mysqlTable("posts", {
id: varchar("id", { length: 36 }).primaryKey().$defaultFn(() => crypto.randomUUID()),
userId: varchar("user_id", { length: 36 }).notNull(),
content: text("content").notNull(),
images: json("images").$type<string[]>(), // 이미지 URL 배열
createdAt: timestamp("created_at").defaultNow().notNull(),
updatedAt: timestamp("updated_at").defaultNow().notNull(),
});
export type Post = typeof posts.$inferSelect;
export type NewPost = typeof posts.$inferInsert;
4단계: 데이터베이스 연결 설정
lib/db/index.ts 파일 생성
// lib/db/index.ts
import { drizzle } from "drizzle-orm/mysql2";
import mysql from "mysql2/promise";
import * as schema from "./schema";
// MySQL 연결 풀 생성
const connection = mysql.createPool({
host: process.env.DB_HOST!,
port: Number(process.env.DB_PORT),
user: process.env.DB_USER!,
password: process.env.DB_PASSWORD!,
database: process.env.DB_NAME!,
});
// Drizzle 인스턴스 생성
export const db = drizzle(connection, {
schema,
mode: "default"
});
5단계: 마이그레이션 실행
5.1 마이그레이션 파일 생성
Drizzle Kit은 두 가지 마이그레이션 방식을 제공한다.
방법 1: Push 명령어 (빠른 개발용)
로컬 개발 환경에서 빠르게 테스트할 때 사용
npx drizzle-kit push
특징
1. 마이그레이션 파일 생성 없이 직접 DB에 반영
2. 스키마 변경사항을 즉시 확인 가능
3. 로컬 개발용으로 적합
4. 변경 이력이 파일로 남지 않음
방법 2: Generate + Migrate (프로덕션용)
프로덕션 환경이나 팀 협업에서 사용
1단계: 마이그레이션 파일 생성
npx drizzle-kit generate
이 명령어는 `/drizzle` 폴더에 SQL 파일을 생성 한다.
2단계: DB에 적용
npx drizzle-kit migrate
Next.js 프로젝트에 Drizzle ORM을 도입하면서 데이터베이스 작업 방식이 크게 개선된걸 느낄 수 있었다.
예전에 한창 백엔드 작업을 했을때는 쌩으로 SQL 문자열을 작성하면서 DB 관리를 했었는데
확실해 ORM을 도입해 작업을 하니 문자열 오타로 인한 시간 낭비를 많이 줄일 수 있었다.
그렇기 때문에 다른 로직 작업에 더 시간을 투자할 수 있어 좋았다.
실제로 타입 안정성이 보장된 쿼리를 작성하다 보니 개발 효율성이 많이 증가한걸 느꼈다.
이제 기본적인 User와 Post 테이블을 구축했으니 다른 테이블들도 타입 안전하게 빠르게 구현할수 있을 것 같다.
[참고]
https://orm.drizzle.team/docs/get-started/mysql-new
Drizzle ORM - MySQL
Drizzle ORM is a lightweight and performant TypeScript ORM with developer experience in mind.
orm.drizzle.team
'개발 > Next.js' 카테고리의 다른 글
| Next.js의 Image 최적화 (0) | 2025.10.16 |
|---|---|
| Next.js 15에서 params가 Promise로 바뀌면서 생긴 빌드 에러 해결기 (0) | 2025.09.21 |