Vercel Postgres와 Drizzle ORM 설정하기
이 튜토리얼은 Vercel Postgres 와 Drizzle ORM을 함께 사용하는 방법을 보여줍니다. Vercel Postgres는 Vercel Functions와 여러분의 프론트엔드 프레임워크와 통합되도록 설계된 서버리스 SQL 데이터베이스입니다.
This guide assumes familiarity with:
Drizzle ORM과 Drizzle kit 을 설치해야 합니다. 다음 명령어를 실행하여 설치할 수 있습니다:
npm i drizzle-orm
npm i -D drizzle-kit
yarn add drizzle-orm
yarn add -D drizzle-kit
pnpm add drizzle-orm
pnpm add -D drizzle-kit
bun add drizzle-orm
bun add -D drizzle-kit
환경 변수를 관리하기 위해 dotenv
패키지를 설치해야 합니다. 이 패키지에 대한 자세한 내용은 여기 에서 확인할 수 있습니다.
npm i dotenv
yarn add dotenv
pnpm add dotenv
bun add dotenv
@vercel/postgres
패키지를 설치해야 합니다. 이 패키지에 대한 자세한 내용은 여기 에서 확인할 수 있습니다.
npm i @vercel/postgres
yarn add @vercel/postgres
pnpm add @vercel/postgres
bun add @vercel/postgres
Drizzle ORM을 사용하여 데이터베이스에 연결하는 방법은 Vercel 문서 를 참고하세요.
새로운 Vercel Postgres 데이터베이스 생성하기 Vercel 대시보드 에서 새로운 Vercel Postgres 데이터베이스를 생성할 수 있습니다.
새로운 데이터베이스를 만드는 방법은 Vercel Postgres 공식 문서 를 참고하세요.
연결 문자열 변수 설정하기
Vercel Postgres 데이터베이스로 이동합니다.
.env.local
섹션에서 POSTGRES_URL
을 복사합니다.
.env.local
또는 .env
파일에 POSTGRES_URL
을 추가합니다.
POSTGRES_URL=
이제 여러분의 프로젝트에서 데이터베이스 연결을 설정할 준비가 되었습니다.
Drizzle ORM을 데이터베이스에 연결하기 src/db
디렉토리에 index.ts
파일을 생성하고 데이터베이스 설정을 구성합니다:
// src/db/index.ts
import { drizzle } from 'drizzle-orm/vercel-postgres' ;
import { config } from 'dotenv' ;
// .env.local 또는 .env 파일에서 환경 변수 로드
config ({ path : '.env.local' });
// Drizzle ORM 인스턴스 생성
export const db = drizzle ();
이 코드는 Drizzle ORM을 사용하여 데이터베이스에 연결하는 기본 설정을 보여줍니다. 환경 변수 파일(.env.local
또는 .env
)에서 설정을 읽어와 데이터베이스 연결을 초기화합니다.
테이블 생성하기 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 () , // 기본 키로 설정된 자동 증가 ID
name : text ( 'name' ) .notNull () , // 이름 필드 (널 값 허용 안 함)
age : integer ( 'age' ) .notNull () , // 나이 필드 (널 값 허용 안 함)
email : text ( 'email' ) .notNull () .unique () , // 이메일 필드 (널 값 허용 안 함, 고유 값)
});
export const postsTable = pgTable ( 'posts_table' , {
id : serial ( 'id' ) .primaryKey () , // 기본 키로 설정된 자동 증가 ID
title : text ( 'title' ) .notNull () , // 제목 필드 (널 값 허용 안 함)
content : text ( 'content' ) .notNull () , // 내용 필드 (널 값 허용 안 함)
userId : integer ( 'user_id' ) // 사용자 ID 필드
.notNull () // 널 값 허용 안 함
.references (() => usersTable .id , { onDelete : 'cascade' }) , // usersTable의 id를 참조, 삭제 시 연쇄 삭제
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 설정 파일 은 Drizzle Kit 에서 사용하는 구성 파일로, 데이터베이스 연결 정보, 마이그레이션 폴더, 스키마 파일 등에 대한 모든 정보를 담고 있습니다.
프로젝트 루트에 drizzle.config.ts
파일을 생성하고 다음 내용을 추가하세요:
import { config } from 'dotenv' ;
import { defineConfig } from 'drizzle-kit' ;
// .env.local 파일에서 환경 변수 로드
config ({ path : '.env.local' });
export default defineConfig ({
schema : './src/db/schema.ts' , // 스키마 파일 경로
out : './migrations' , // 마이그레이션 파일이 생성될 경로
dialect : 'postgresql' , // 데이터베이스 종류
dbCredentials : {
url : process . env . POSTGRES_URL ! , // 데이터베이스 연결 URL
} ,
});
이 설정 파일은 Drizzle Kit이 데이터베이스 스키마를 관리하고 마이그레이션을 생성하는 데 필요한 정보를 제공합니다.
데이터베이스에 변경사항 적용하기 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)별로 파일을 분리합니다.
데이터 삽입
데이터 삽입 쿼리에 대한 자세한 내용은 문서 를 참고하세요.
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 쿼리에 대해 더 알아보세요.
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);
}
또는 관계형 쿼리 구문 을 사용할 수도 있습니다.
데이터 업데이트
데이터 업데이트 쿼리에 대한 자세한 내용은 문서 를 참고하세요.
import { eq } from 'drizzle-orm' ;
import { db } from '../index' ;
import { SelectPost , postsTable } from '../schema' ;
export async function updatePost (id : SelectPost [ 'id' ] , data : Partial < SelectPost >) {
await db .update (postsTable) .set (data) .where ( eq ( postsTable .id , id));
}
데이터 삭제
삭제 쿼리에 대해 더 자세히 알아보려면 문서 를 참고하세요.
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));
}