Drizzle과 Xata

이 튜토리얼은 Drizzle ORM을 Xata와 함께 사용하는 방법을 설명합니다. Xata는 개발자가 향상된 생산성과 성능으로 데이터베이스를 운영하고 확장할 수 있도록 설계된 PostgreSQL 데이터베이스 플랫폼으로, 즉시 사용 가능한 copy-on-write 데이터베이스 브랜치, 무중단 스키마 변경, 데이터 익명화, AI 기반 성능 모니터링 기능을 제공합니다.

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
  • Postgres 데이터베이스 연결을 위한 postgres 패키지가 설치되어 있어야 합니다. 이 패키지에 대한 자세한 내용은 여기에서 확인하세요
npm
yarn
pnpm
bun
npm i postgres
  • Xata 계정과 데이터베이스가 설정되어 있어야 합니다. 계정과 데이터베이스 생성 방법은 Xata 문서를 참조하세요

Drizzle ORM을 Xata와 함께 사용하는 방법에 대한 자세한 내용은 Xata 문서를 참조하세요.

Xata와 Drizzle ORM 설정

새로운 Xata 데이터베이스 생성

다음 단계에 따라 새로운 Xata 데이터베이스를 생성할 수 있습니다:

  1. Xata 계정에 가입하거나 로그인합니다
  2. 대시보드에서 새로운 데이터베이스를 생성합니다
  3. 리전과 데이터베이스 이름을 선택합니다
  4. PostgreSQL 엔드포인트가 포함된 데이터베이스가 생성됩니다

연결 문자열 변수 설정

Xata 대시보드로 이동하여 PostgreSQL 연결 문자열을 복사합니다. 브랜치 개요 페이지에서 찾을 수 있습니다.

.env 또는 .env.local 파일에 DATABASE_URL 변수를 추가합니다:

DATABASE_URL=<YOUR_XATA_DATABASE_URL>

연결 문자열 형식은 다음과 같습니다:

postgresql://postgres:<password>@<branch-id>.<region>.xata.tech/<database>?sslmode=require

예시:

postgresql://postgres:password@t56hgfp7hd2sjfeiqcn66qpo8s.us-east-1.xata.tech/app?sslmode=require

Xata는 브랜치 기반 개발을 제공하여 개발, 스테이징, 프로덕션 환경을 위한 격리된 데이터베이스 브랜치를 생성할 수 있습니다.

Drizzle ORM을 데이터베이스에 연결

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

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

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

const client = postgres(process.env.DATABASE_URL!);
export const db = drizzle({ client });

테이블 생성

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' });

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

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

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

마이그레이션 생성:

npx drizzle-kit generate

이러한 마이그레이션은 drizzle.config.ts에 지정된 대로 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 명령어는 로컬 개발 환경에서 새로운 스키마 디자인이나 변경사항을 빠르게 테스트해야 하는 상황에 적합하며, 마이그레이션 파일 관리의 오버헤드 없이 빠른 반복 작업을 가능하게 합니다.

Xata 브랜치 기반 개발: Xata를 사용하면 다양한 환경을 위한 데이터베이스 브랜치를 생성할 수 있습니다. 개발, 스테이징, 프로덕션 브랜치에 대해 서로 다른 연결 문자열을 사용할 수 있어 프로덕션에 배포하기 전에 스키마 변경사항을 쉽게 테스트할 수 있습니다.

기본 파일 구조

다음은 프로젝트의 기본 파일 구조입니다. 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
 ├ 📜 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 쿼리에 대한 자세한 내용은 문서를 참조하세요.

IMPORTANT

getColumnsdrizzle-orm@1.0.0-beta.2부터 사용 가능합니다 (자세한 내용은 여기에서 확인하세요)

0.45.1과 같은 1.0 이전 버전을 사용하는 경우 getTableColumns를 사용하세요

src/db/queries/select.ts
import { asc, between, count, eq, getColumns, 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({
      ...getColumns(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));
}

다음 단계

Drizzle ORM을 Xata와 함께 성공적으로 설정했으므로 더 고급 기능을 살펴볼 수 있습니다: