Xata는 브랜치 기반 개발을 제공하여 개발, 스테이징, 프로덕션 환경을 위한 격리된 데이터베이스 브랜치를 생성할 수 있습니다.
Drizzle과 Xata
이 튜토리얼은 Drizzle ORM을 Xata와 함께 사용하는 방법을 설명합니다. Xata는 개발자가 향상된 생산성과 성능으로 데이터베이스를 운영하고 확장할 수 있도록 설계된 PostgreSQL 데이터베이스 플랫폼으로, 즉시 사용 가능한 copy-on-write 데이터베이스 브랜치, 무중단 스키마 변경, 데이터 익명화, AI 기반 성능 모니터링 기능을 제공합니다.
- Drizzle ORM과 Drizzle kit이 설치되어 있어야 합니다. 다음 명령어를 실행하여 설치할 수 있습니다:
npm i drizzle-orm
npm i -D drizzle-kit
- 환경 변수 관리를 위한
dotenv패키지가 설치되어 있어야 합니다. 이 패키지에 대한 자세한 내용은 여기에서 확인하세요
npm i dotenv
- Postgres 데이터베이스 연결을 위한
postgres패키지가 설치되어 있어야 합니다. 이 패키지에 대한 자세한 내용은 여기에서 확인하세요
npm i postgres
- Xata 계정과 데이터베이스가 설정되어 있어야 합니다. 계정과 데이터베이스 생성 방법은 Xata 문서를 참조하세요
Drizzle ORM을 Xata와 함께 사용하는 방법에 대한 자세한 내용은 Xata 문서를 참조하세요.
Xata와 Drizzle ORM 설정
새로운 Xata 데이터베이스 생성
다음 단계에 따라 새로운 Xata 데이터베이스를 생성할 수 있습니다:
- Xata 계정에 가입하거나 로그인합니다
- 대시보드에서 새로운 데이터베이스를 생성합니다
- 리전과 데이터베이스 이름을 선택합니다
- 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=requireDrizzle ORM을 데이터베이스에 연결
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 파일을 생성하고 테이블을 정의합니다:
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 파일을 생성하고 다음 내용을 추가합니다:
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 pushXata 브랜치 기반 개발: 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 쿼리에 대한 자세한 내용은 문서를 참조하세요.
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 쿼리에 대한 자세한 내용은 문서를 참조하세요.
getColumns는 drizzle-orm@1.0.0-beta.2부터 사용 가능합니다 (자세한 내용은 여기에서 확인하세요)
0.45.1과 같은 1.0 이전 버전을 사용하는 경우 getTableColumns를 사용하세요
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 쿼리에 대한 자세한 내용은 문서를 참조하세요.
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 쿼리에 대한 자세한 내용은 문서를 참조하세요.
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와 함께 성공적으로 설정했으므로 더 고급 기능을 살펴볼 수 있습니다:
- 복잡한 쿼리를 위한 Drizzle 관계에 대해 알아보기
- Xata 문서 살펴보기
- 프로덕션 배포를 위한 데이터베이스 마이그레이션 구현하기