๋ง์ด๊ทธ๋ ์ด์ ํ๋ก์ธ์ค์ Drizzle-Kit์ ์ฌ์ฉํ๋ ๊ฒฝ์ฐ, Drizzle-Kit์ด ์ด๋ฅผ ๊ฐ์ ธ์์ ๋ง์ด๊ทธ๋ ์ด์ diff ํ๋ก์ธ์ค์์ ์ฌ์ฉํ ์ ์๋๋ก ์คํค๋ง ํ์ผ์ ์ ์๋ ๋ชจ๋ ๋ชจ๋ธ์ ๋ด๋ณด๋ด์ผ ํฉ๋๋ค.
Drizzle schema
Drizzle๋ฅผ ์ฌ์ฉํ๋ฉด ๊ธฐ๋ณธ ๋ฐ์ดํฐ๋ฒ ์ด์ค์์ ์ง์ํ๋ ๋ค์ํ ๋ชจ๋ธ๊ณผ ์์ฑ์ TypeScript๋ก ์คํค๋ง๋ฅผ ์ ์ํ ์ ์์ต๋๋ค. ์คํค๋ง๋ฅผ ์ ์ํ๋ฉด, ์ด๋ ์ฟผ๋ฆฌ(Drizzle-ORM ์ฌ์ฉ)์ ๋ง์ด๊ทธ๋ ์ด์ (Drizzle-Kit ์ฌ์ฉ)์ ํฅํ ์์ ์ ๋ํ ๋จ์ผ ์์ค๋ก ์๋ํฉ๋๋ค.
์คํค๋ง ํ์ผ ๊ตฌ์ฑํ๊ธฐ
SQL ์คํค๋ง๋ฅผ TypeScript์์ ์ง์ ์ ์ธํ ์ ์์ต๋๋ค. ๋จ์ผ schema.ts ํ์ผ์ ์ ์ธํ๊ฑฐ๋,
์ฌ๋ฌ ํ์ผ์ ๋ถ์ฐ์ํฌ ์ ์์ต๋๋ค โ ์ํ๋ ๋๋ก ์ ํํ์ธ์. ์์ ํ ์์ ์
๋๋ค!
๋จ์ผ ํ์ผ ์คํค๋ง
Drizzle๋ก ์คํค๋ง๋ฅผ ์ ์ธํ๋ ๊ฐ์ฅ ์ผ๋ฐ์ ์ธ ๋ฐฉ๋ฒ์ ๋ชจ๋ ํ
์ด๋ธ์ ํ๋์ schema.ts ํ์ผ์ ๋ฃ๋ ๊ฒ์
๋๋ค.
์ฐธ๊ณ : ์คํค๋ง ํ์ผ์ ์ด๋ฆ์ ์ํ๋ ๋๋ก ์ง์ ํ ์ ์์ต๋๋ค. ์๋ฅผ ๋ค์ด
models.ts๋ ๋ค๋ฅธ ์ด๋ฆ๋ ๊ฐ๋ฅํฉ๋๋ค.
์ด ์ ๊ทผ ๋ฐฉ์์ ์ ์๋ ํ ์ด๋ธ ๋ชจ๋ธ์ด ๋๋ฌด ๋ง์ง ์๊ฑฐ๋, ๋ชจ๋ ํ ์ด๋ธ์ ํ๋์ ํ์ผ์ ์ ์งํ๋ ๊ฒ์ด ๊ด์ฐฎ์ ๊ฒฝ์ฐ์ ์ ํฉํฉ๋๋ค
์์:
๐ฆ <project root>
โ ๐ src
โ ๐ db
โ ๐ schema.tsdrizzle.config.ts ํ์ผ์์ ์คํค๋ง ํ์ผ์ ๊ฒฝ๋ก๋ฅผ ์ง์ ํด์ผ ํฉ๋๋ค. ์ด ์ค์ ์ ์ฌ์ฉํ๋ฉด Drizzle์ด
schema.ts ํ์ผ์ ์ฝ๊ณ ๋ง์ด๊ทธ๋ ์ด์
์์ฑ ํ๋ก์ธ์ค์์ ์ด ์ ๋ณด๋ฅผ ์ฌ์ฉํฉ๋๋ค. drizzle.config.ts ํ์ผ๊ณผ
Drizzle ๋ง์ด๊ทธ๋ ์ด์
์ ๋ํ ์์ธํ ๋ด์ฉ์ ๋ค์์ ์ฐธ์กฐํ์ธ์: ๋งํฌ
import { defineConfig } from "drizzle-kit";
export default defineConfig({
dialect: 'postgresql', // 'mysql' | 'sqlite' | 'turso'
schema: './src/db/schema.ts'
})์ฌ๋ฌ ํ์ผ๋ก ๊ตฌ์ฑ๋ ์คํค๋ง
Drizzle ๋ชจ๋ธ(ํ ์ด๋ธ, ์ด๊ฑฐํ, ์ํ์ค ๋ฑ)์ ํ๋์ ํ์ผ๋ฟ๋ง ์๋๋ผ ์ํ๋ ๋ชจ๋ ํ์ผ์ ๋ฐฐ์นํ ์ ์์ต๋๋ค. ๋จ, Drizzle Kit์ด ์ด๋ฅผ ๊ฐ์ ธ์์ ๋ง์ด๊ทธ๋ ์ด์ ์ ์ฌ์ฉํ ์ ์๋๋ก ํด๋น ํ์ผ์์ ๋ชจ๋ ๋ชจ๋ธ์ ๋ด๋ณด๋ด์ผ ํฉ๋๋ค.
ํ๋์ ์ฌ์ฉ ์ฌ๋ก๋ ๊ฐ ํ ์ด๋ธ์ ๋ณ๋์ ํ์ผ๋ก ๋ถ๋ฆฌํ๋ ๊ฒ์ ๋๋ค.
๐ฆ <project root>
โ ๐ src
โ ๐ db
โ ๐ schema
โ ๐ users.ts
โ ๐ countries.ts
โ ๐ cities.ts
โ ๐ products.ts
โ ๐ clients.ts
โ ๐ etc.tsdrizzle.config.ts ํ์ผ์์ ์คํค๋ง ํด๋์ ๊ฒฝ๋ก๋ฅผ ์ง์ ํด์ผ ํฉ๋๋ค. ์ด ์ค์ ์ ์ฌ์ฉํ๋ฉด Drizzle์ด
schema ํด๋๋ฅผ ์ฝ๊ณ ๋ชจ๋ ํ์ผ์ ์ฌ๊ท์ ์ผ๋ก ์ฐพ์์ ๋ชจ๋ Drizzle ํ
์ด๋ธ์ ๊ฐ์ ธ์ต๋๋ค. drizzle.config.ts ํ์ผ๊ณผ
Drizzle ๋ง์ด๊ทธ๋ ์ด์
์ ๋ํ ์์ธํ ๋ด์ฉ์ ๋ค์์ ์ฐธ์กฐํ์ธ์: ๋งํฌ
import { defineConfig } from "drizzle-kit";
export default defineConfig({
dialect: 'postgresql', // 'mysql' | 'sqlite' | 'turso'
schema: './src/db/schema'
})์ํ๋ ๋ฐฉ์์ผ๋ก ๊ทธ๋ฃนํํ ์๋ ์์ต๋๋ค. ์๋ฅผ ๋ค์ด ์ฌ์ฉ์ ๊ด๋ จ ํ ์ด๋ธ, ๋ฉ์์ง ๊ด๋ จ ํ ์ด๋ธ, ์ ํ ๊ด๋ จ ํ ์ด๋ธ ๋ฑ์ ๊ทธ๋ฃน์ ์์ฑํ ์ ์์ต๋๋ค.
๐ฆ <project root>
โ ๐ src
โ ๐ db
โ ๐ schema
โ ๐ users.ts
โ ๐ messaging.ts
โ ๐ products.ts๋ฐ์ดํฐ ์คํค๋ง ์ ์ํ๊ธฐ
Drizzle ์คํค๋ง๋ ์ฌ์ฉ ์ค์ธ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ์ฌ๋ฌ ๋ชจ๋ธ ์ ํ์ผ๋ก ๊ตฌ์ฑ๋ฉ๋๋ค. Drizzle์ ์ฌ์ฉํ๋ฉด ๋ค์์ ์ง์ ํ ์ ์์ต๋๋ค:
- ์ด, ์ ์ฝ ์กฐ๊ฑด ๋ฑ์ด ์๋ ํ ์ด๋ธ
- ์คํค๋ง(PostgreSQL๋ง ํด๋น)
- ์ด๊ฑฐํ
- ์ํ์ค(PostgreSQL๋ง ํด๋น)
- ๋ทฐ
- ๊ตฌ์ฒดํ๋ ๋ทฐ
- ๊ธฐํ
ํ๋์ฉ ์ดํด๋ณด๋ฉด์ Drizzle๋ก ์คํค๋ง๋ฅผ ์ ์ํ๋ ๋ฐฉ๋ฒ์ ํ์ธํด ๋ณด๊ฒ ์ต๋๋ค
ํ ์ด๋ธ ๋ฐ ์ด ์ ์ธ
Drizzle์ ํ ์ด๋ธ์ ๋ฐ์ดํฐ๋ฒ ์ด์ค์์์ ๋ง์ฐฌ๊ฐ์ง๋ก ์ต์ 1๊ฐ์ ์ด๋ก ์ ์๋์ด์ผ ํฉ๋๋ค. ์์์ผ ํ ์ค์ํ ์ ์ Drizzle์๋ ๊ณตํต ํ ์ด๋ธ ๊ฐ์ฒด์ ๊ฐ์ ๊ฒ์ด ์๋ค๋ ๊ฒ์ ๋๋ค. ์ฌ์ฉ ์ค์ธ ๋ฐฉ์ธ(PostgreSQL, MySQL ๋๋ SQLite)์ ์ ํํด์ผ ํฉ๋๋ค.
import { pgTable, integer } from "drizzle-orm/pg-core"
export const users = pgTable('users', {
id: integer()
});๊ธฐ๋ณธ์ ์ผ๋ก Drizzle์ ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ฟผ๋ฆฌ์์ ์ด์ ๋ํด TypeScript ํค ์ด๋ฆ์ ์ฌ์ฉํฉ๋๋ค. ๋ฐ๋ผ์ ์์ ์ ์คํค๋ง์ ์ฟผ๋ฆฌ๋ ์๋์ ๊ฐ์ SQL ์ฟผ๋ฆฌ๋ฅผ ์์ฑํฉ๋๋ค.
์ด ์์ ๋ db ๊ฐ์ฒด๋ฅผ ์ฌ์ฉํ๋ฉฐ, ๊ทธ ์ด๊ธฐํ๋ ๋ฌธ์์ ์ด ๋ถ๋ถ์์ ๋ค๋ฃจ์ง ์์ต๋๋ค. ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ฐ๊ฒฐ ๋ฐฉ๋ฒ์ ์์๋ณด๋ ค๋ฉด ์ฐ๊ฒฐ ๋ฌธ์๋ฅผ ์ฐธ์กฐํ์ธ์.
TypeScript ํค = ๋ฐ์ดํฐ๋ฒ ์ด์ค ํค
// schema.ts
import { integer, pgTable, varchar } from "drizzle-orm/pg-core";
export const users = pgTable('users', {
id: integer(),
first_name: varchar()
})// query.ts
await db.select().from(users);SELECT "id", "first_name" from users;TypeScript ์ฝ๋์ ๋ฐ์ดํฐ๋ฒ ์ด์ค์์ ๋ค๋ฅธ ์ด๋ฆ์ ์ฌ์ฉํ๋ ค๋ฉด ์ด ๋ณ์นญ์ ์ฌ์ฉํ ์ ์์ต๋๋ค.
// schema.ts
import { integer, pgTable, varchar } from "drizzle-orm/pg-core";
export const users = pgTable('users', {
id: integer(),
firstName: varchar('first_name')
})// query.ts
await db.select().from(users);SELECT "id", "first_name" from users;์นด๋ฉ ์ผ์ด์ค์ ์ค๋ค์ดํฌ ์ผ์ด์ค
๋ฐ์ดํฐ๋ฒ ์ด์ค ๋ชจ๋ธ ์ด๋ฆ์ ์ข
์ข
snake_case ๊ท์น์ ์ฌ์ฉํ๋ ๋ฐ๋ฉด, TypeScript์์๋ ๋ชจ๋ธ ์ด๋ฆ์ camelCase๋ฅผ ์ฌ์ฉํ๋ ๊ฒ์ด ์ผ๋ฐ์ ์
๋๋ค.
์ด๋ก ์ธํด ์คํค๋ง์ ๋ง์ ๋ณ์นญ ์ ์๊ฐ ํ์ํ ์ ์์ต๋๋ค. ์ด๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด Drizzle์ Drizzle ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ด๊ธฐํ ์
ํ๋์ ์ ํ์ ๋งค๊ฐ๋ณ์๋ฅผ ํฌํจํ์ฌ TypeScript์ camelCase๋ฅผ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ snake_case๋ก ์๋ ๋งคํํ๋ ๋ฐฉ๋ฒ์ ์ ๊ณตํฉ๋๋ค.
์ด๋ฌํ ๋งคํ์ ์ํด Drizzle DB ์ ์ธ์์ casing ์ต์
์ ์ฌ์ฉํ ์ ์์ต๋๋ค. ์ด ๋งค๊ฐ๋ณ์๋
๋ฐ์ดํฐ๋ฒ ์ด์ค ๋ชจ๋ธ ๋ช
๋ช
๊ท์น์ ์ง์ ํ๋ ๋ฐ ๋์์ด ๋๋ฉฐ ๋ชจ๋ JavaScript ํค๋ฅผ ์ ์ ํ ๋งคํํ๋ ค๊ณ ์๋ํฉ๋๋ค.
// schema.ts
import { drizzle } from "drizzle-orm/node-postgres";
import { integer, pgTable, varchar } from "drizzle-orm/pg-core";
export const users = pgTable('users', {
id: integer(),
firstName: varchar()
})// db.ts
const db = drizzle({ connection: process.env.DATABASE_URL, casing: 'snake_case' })// query.ts
await db.select().from(users);SELECT "id", "first_name" from users;๊ณ ๊ธ ์ฌ์ฉ๋ฒ
Drizzle ORM์์ ์ฌ์ฉํ ์ ์๋ ๋ช ๊ฐ์ง ๊ธฐ๋ฒ์ด ์์ต๋๋ค. Drizzle์ ์์ ํ TypeScript ํ์ผ์ ์์ผ๋ฏ๋ก ์ผ๋ฐ TypeScript ํ๋ก์ ํธ์์ ์ฝ๋๋ก ํ ์ ์๋ ๋ชจ๋ ์์ ์ ๋ณธ์ง์ ์ผ๋ก ์ํํ ์ ์์ต๋๋ค.
์ผ๋ฐ์ ์ธ ๊ธฐ๋ฅ ์ค ํ๋๋ ์ด์ ์๋ก ๋ค๋ฅธ ์์น๋ก ๋ถ๋ฆฌํ ๋ค์ ์ฌ์ฌ์ฉํ๋ ๊ฒ์
๋๋ค.
์๋ฅผ ๋ค์ด updated_at, created_at, deleted_at ์ด์ ๊ณ ๋ คํด๋ณด์ธ์. ๋ง์ ํ
์ด๋ธ/๋ชจ๋ธ์ด ์์คํ
์์
์ํฐํฐ์ ์์ฑ, ์ญ์ ๋ฐ ์
๋ฐ์ดํธ๋ฅผ ์ถ์ ํ๊ณ ๋ถ์ํ๊ธฐ ์ํด ์ด ์ธ ๊ฐ์ง ํ๋๋ฅผ ํ์๋ก ํ ์ ์์ต๋๋ค.
์ด๋ฌํ ์ด์ ๋ณ๋์ ํ์ผ์ ์ ์ํ ๋ค์ ๊ฐ์ ธ์์ ๋ชจ๋ ํ ์ด๋ธ ๊ฐ์ฒด์ ์ ๊ฐํ ์ ์์ต๋๋ค.
// columns.helpers.ts
const timestamps = {
updated_at: timestamp(),
created_at: timestamp().defaultNow().notNull(),
deleted_at: timestamp(),
}// users.sql.ts
export const users = pgTable('users', {
id: integer(),
...timestamps
})// posts.sql.ts
export const posts = pgTable('posts', {
id: integer(),
...timestamps
})์คํค๋ง
PostgreSQL์๋ schema๋ผ๋ ์ํฐํฐ๊ฐ ์์ต๋๋ค(์ฐ๋ฆฌ๋ ์ด๋ฅผ folders๋ผ๊ณ ๋ถ๋ฌ์ผ ํ๋ค๊ณ ์๊ฐํฉ๋๋ค). ์ด๋ PostgreSQL์์ ๋ค์๊ณผ ๊ฐ์ ๊ตฌ์กฐ๋ฅผ ๋ง๋ญ๋๋ค:

pgSchema๋ก PostgreSQL ์คํค๋ง๋ฅผ ๊ด๋ฆฌํ๊ณ ๊ทธ ์์ ๋ค๋ฅธ ๋ชจ๋ธ์ ๋ฐฐ์นํ ์ ์์ต๋๋ค.
Drizzle์ ์ฌ์ฉํ์ฌ ๊ด๋ฆฌํ๋ ค๋ ์คํค๋ง๋ฅผ ์ ์ํฉ๋๋ค.
import { pgSchema } from "drizzle-orm/pg-core"
export const customSchema = pgSchema('custom');๊ทธ๋ฐ ๋ค์ ์คํค๋ง ๊ฐ์ฒด ์์ ํ ์ด๋ธ์ ๋ฐฐ์นํฉ๋๋ค.
import { integer, pgSchema } from "drizzle-orm/pg-core";
export const customSchema = pgSchema('custom');
export const users = customSchema.table('users', {
id: integer()
})์์
๊ธฐ๋ณธ ์ฌํญ์ ์์์ผ๋ ์ค์ ํ๋ก์ ํธ์ ๋ํ ์คํค๋ง ์์ ๋ฅผ ์ ์ํ์ฌ ๋ ๋์ ์ดํด๋ฅผ ์ป์ด๋ณด๊ฒ ์ต๋๋ค.
๋ชจ๋ ์์ ๋
generateUniqueString์ ์ฌ์ฉํฉ๋๋ค. ๊ตฌํ์ ๋ชจ๋ ์คํค๋ง ์์ ์ดํ์ ์ ๊ณต๋ฉ๋๋ค.
import { AnyPgColumn } from "drizzle-orm/pg-core";
import { pgEnum, pgTable as table } from "drizzle-orm/pg-core";
import * as t from "drizzle-orm/pg-core";
export const rolesEnum = pgEnum("roles", ["guest", "user", "admin"]);
export const users = table(
"users",
{
id: t.integer().primaryKey().generatedAlwaysAsIdentity(),
firstName: t.varchar("first_name", { length: 256 }),
lastName: t.varchar("last_name", { length: 256 }),
email: t.varchar().notNull(),
invitee: t.integer().references((): AnyPgColumn => users.id),
role: rolesEnum().default("guest"),
},
(table) => [
t.uniqueIndex("email_idx").on(table.email)
]
);
export const posts = table(
"posts",
{
id: t.integer().primaryKey().generatedAlwaysAsIdentity(),
slug: t.varchar().$default(() => generateUniqueString(16)),
title: t.varchar({ length: 256 }),
ownerId: t.integer("owner_id").references(() => users.id),
},
(table) => [
t.uniqueIndex("slug_idx").on(table.slug),
t.index("title_idx").on(table.title),
]
);
export const comments = table("comments", {
id: t.integer().primaryKey().generatedAlwaysAsIdentity(),
text: t.varchar({ length: 256 }),
postId: t.integer("post_id").references(() => posts.id),
ownerId: t.integer("owner_id").references(() => users.id),
});generateUniqueString ๊ตฌํ:
function generateUniqueString(length: number = 12): string {
const characters =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
let uniqueString = "";
for (let i = 0; i < length; i++) {
const randomIndex = Math.floor(Math.random() * characters.length);
uniqueString += characters[randomIndex];
}
return uniqueString;
}๋ค์ ๋จ๊ณ
์คํค๋ง ๊ด๋ฆฌ
๊ธฐ์ด๋ถํฐ ์์

