Vercel Postgres와 함께 Drizzle 사용하기

이 튜토리얼은 Vercel Postgres와 함께 Drizzle ORM을 사용하는 방법을 보여줍니다. Vercel Postgres는 Vercel Functions 및 프론트엔드 프레임워크와 통합되도록 설계된 서버리스 SQL 데이터베이스입니다.

This guide assumes familiarity with:
  • Drizzle ORM과 Drizzle kit를 설치해야 합니다. 다음 명령어를 실행하여 설치할 수 있습니다:
npm
yarn
pnpm
bun
npm i drizzle-orm
npm i -D drizzle-kit
  • 환경 변수 관리를 위해 dotenv 패키지를 설치해야 합니다. 이 패키지에 대한 자세한 내용은 여기를 참조하세요
npm
yarn
pnpm
bun
npm i dotenv
  • @vercel/postgres 패키지를 설치해야 합니다. 이 패키지에 대한 자세한 내용은 여기를 참조하세요
npm
yarn
pnpm
bun
npm i @vercel/postgres

Drizzle ORM으로 데이터베이스에 연결하는 방법은 Vercel 문서를 확인하세요.

Vercel Postgres와 Drizzle ORM 설정하기

새로운 Vercel Postgres 데이터베이스 생성하기

대시보드에서 새로운 Vercel Postgres 데이터베이스를 생성할 수 있습니다.

새 데이터베이스를 생성하는 방법은 Vercel Postgres 문서를 참조하세요.

연결 문자열 변수 설정하기

Vercel Postgres 데이터베이스로 이동하여 .env.local 섹션에서 POSTGRES_URL을 복사합니다.

POSTGRES_URL.env.local 또는 .env 파일에 추가합니다.

POSTGRES_URL=<YOUR_DATABASE_URL>

데이터베이스에 Drizzle ORM 연결하기

src/db 디렉토리에 index.ts 파일을 생성하고 데이터베이스 구성을 설정합니다:

src/db/index.ts
import { drizzle } from 'drizzle-orm/vercel-postgres';
import { config } from 'dotenv';

config({ path: '.env.local' }); // or .env

export const db = drizzle();

테이블 생성하기

src/db 디렉토리에 schema.ts 파일을 생성하고 테이블을 선언합니다:

src/db/schema.ts
import { integer, pgTable, serial, text, timestamp } from 'drizzle-orm/pg-core';

export const usersTable = pgTable('users_table', {
  id: serial('id').primaryKey(),
  name: text('name').notNull(),
  age: integer('age').notNull(),
  email: text('email').notNull().unique(),
});

export const postsTable = pgTable('posts_table', {
  id: serial('id').primaryKey(),
  title: text('title').notNull(),
  content: text('content').notNull(),
  userId: integer('user_id')
    .notNull()
    .references(() => usersTable.id, { onDelete: 'cascade' }),
  createdAt: timestamp('created_at').notNull().defaultNow(),
  updatedAt: timestamp('updated_at')
    .notNull()
    .$onUpdate(() => new Date()),
});

export type InsertUser = typeof usersTable.$inferInsert;
export type SelectUser = typeof usersTable.$inferSelect;

export type InsertPost = typeof postsTable.$inferInsert;
export type SelectPost = typeof postsTable.$inferSelect;

Drizzle 구성 파일 설정하기

Drizzle config - Drizzle Kit에서 사용되는 구성 파일로, 데이터베이스 연결, 마이그레이션 폴더 및 스키마 파일에 대한 모든 정보를 포함합니다.

프로젝트 루트에 drizzle.config.ts 파일을 생성하고 다음 내용을 추가합니다:

drizzle.config.ts
import { config } from 'dotenv';
import { defineConfig } from 'drizzle-kit';

config({ path: '.env.local' });

export default defineConfig({
  schema: './src/db/schema.ts',
  out: './migrations',
  dialect: 'postgresql',
  dbCredentials: {
    url: process.env.POSTGRES_URL!,
  },
});

데이터베이스에 변경사항 적용하기

drizzle-kit generate 명령어를 사용하여 마이그레이션을 생성한 다음 drizzle-kit migrate 명령어로 실행할 수 있습니다.

마이그레이션 생성하기:

npx drizzle-kit generate

이러한 마이그레이션은 drizzle.config.ts에 지정된 대로 drizzle/migrations 디렉토리에 저장됩니다. 이 디렉토리에는 데이터베이스 스키마를 업데이트하는 데 필요한 SQL 파일과 다양한 마이그레이션 단계의 스키마 스냅샷을 저장하는 meta 폴더가 포함됩니다.

생성된 마이그레이션 예시:

CREATE TABLE IF NOT EXISTS "posts_table" (
	"id" serial PRIMARY KEY NOT NULL,
	"title" text NOT NULL,
	"content" text NOT NULL,
	"user_id" integer NOT NULL,
	"created_at" timestamp DEFAULT now() NOT NULL,
	"updated_at" timestamp NOT NULL
);
--> statement-breakpoint
CREATE TABLE IF NOT EXISTS "users_table" (
	"id" serial PRIMARY KEY NOT NULL,
	"name" text NOT NULL,
	"age" integer NOT NULL,
	"email" text NOT NULL,
	CONSTRAINT "users_table_email_unique" UNIQUE("email")
);
--> statement-breakpoint
DO $$ BEGIN
 ALTER TABLE "posts_table" ADD CONSTRAINT "posts_table_user_id_users_table_id_fk" FOREIGN KEY ("user_id") REFERENCES "users_table"("id") ON DELETE cascade ON UPDATE no action;
EXCEPTION
 WHEN duplicate_object THEN null;
END $$;

마이그레이션 실행하기:

npx drizzle-kit migrate

또는 Drizzle kit push 명령어를 사용하여 데이터베이스에 변경사항을 직접 푸시할 수 있습니다:

npx drizzle-kit push
IMPORTANT
Push 명령어는 로컬 개발 환경에서 새로운 스키마 디자인이나 변경사항을 빠르게 테스트해야 하는 상황에 적합하며, 마이그레이션 파일 관리의 오버헤드 없이 빠른 반복이 가능합니다.

기본 파일 구조

프로젝트의 기본 파일 구조입니다. src/db 디렉토리에는 index.ts의 연결과 schema.ts의 스키마 정의를 포함한 데이터베이스 관련 파일이 있습니다.

📦 <project root>
 ├ 📂 src
 │   ├ 📂 db
 │   │  ├ 📜 index.ts
 │   │  └ 📜 schema.ts
 ├ 📂 migrations
 │   ├ 📂 meta
 │   │  ├ 📜 _journal.json
 │   │  └ 📜 0000_snapshot.json
 │   └ 📜 0000_watery_spencer_smythe.sql
 ├ 📜 .env.local
 ├ 📜 drizzle.config.ts
 ├ 📜 package.json
 └ 📜 tsconfig.json

쿼리 예제

예를 들어, src/db/queries 폴더를 생성하고 각 작업(insert, select, update, delete)별로 파일을 분리합니다.

데이터 삽입하기

insert 쿼리에 대한 자세한 내용은 문서를 참조하세요.

src/db/queries/insert.ts
import { db } from '../index';
import { InsertPost, InsertUser, postsTable, usersTable } from '../schema';

export async function createUser(data: InsertUser) {
  await db.insert(usersTable).values(data);
}

export async function createPost(data: InsertPost) {
  await db.insert(postsTable).values(data);
}

데이터 조회하기

select 쿼리에 대한 자세한 내용은 문서를 참조하세요.

src/db/queries/select.ts
import { asc, between, count, eq, getTableColumns, sql } from 'drizzle-orm';
import { db } from '../index';
import { SelectUser, postsTable, usersTable } from '../schema';

export async function getUserById(id: SelectUser['id']): Promise<
  Array<{
    id: number;
    name: string;
    age: number;
    email: string;
  }>
> {
  return db.select().from(usersTable).where(eq(usersTable.id, id));
}

export async function getUsersWithPostsCount(
  page = 1,
  pageSize = 5,
): Promise<
  Array<{
    postsCount: number;
    id: number;
    name: string;
    age: number;
    email: string;
  }>
> {
  return db
    .select({
      ...getTableColumns(usersTable),
      postsCount: count(postsTable.id),
    })
    .from(usersTable)
    .leftJoin(postsTable, eq(usersTable.id, postsTable.userId))
    .groupBy(usersTable.id)
    .orderBy(asc(usersTable.id))
    .limit(pageSize)
    .offset((page - 1) * pageSize);
}

export async function getPostsForLast24Hours(
  page = 1,
  pageSize = 5,
): Promise<
  Array<{
    id: number;
    title: string;
  }>
> {
  return db
    .select({
      id: postsTable.id,
      title: postsTable.title,
    })
    .from(postsTable)
    .where(between(postsTable.createdAt, sql`now() - interval '1 day'`, sql`now()`))
    .orderBy(asc(postsTable.title), asc(postsTable.id))
    .limit(pageSize)
    .offset((page - 1) * pageSize);
}

또는 관계형 쿼리 구문을 사용할 수 있습니다.

데이터 업데이트하기

update 쿼리에 대한 자세한 내용은 문서를 참조하세요.

src/db/queries/update.ts
import { eq } from 'drizzle-orm';
import { db } from '../index';
import { SelectPost, postsTable } from '../schema';

export async function updatePost(id: SelectPost['id'], data: Partial<Omit<SelectPost, 'id'>>) {
  await db.update(postsTable).set(data).where(eq(postsTable.id, id));
}

데이터 삭제하기

delete 쿼리에 대한 자세한 내용은 문서를 참조하세요.

src/db/queries/delete.ts
import { eq } from 'drizzle-orm';
import { db } from '../index';
import { SelectUser, usersTable } from '../schema';

export async function deleteUser(id: SelectUser['id']) {
  await db.delete(usersTable).where(eq(usersTable.id, id));
}