Drizzle와 SQLite Durable Objects 시작하기

This guide assumes familiarity with:
  • dotenv - 환경 변수를 관리하기 위한 패키지 - 자세히 보기
  • tsx - TypeScript 파일을 실행하기 위한 패키지 - 자세히 보기
  • Cloudflare SQLite Durable Objects - Durable Object 내에 내장된 SQLite 데이터베이스 - 자세히 보기
  • wrangler - Cloudflare 개발자 플랫폼 커맨드라인 인터페이스 - 자세히 보기

기본 파일 구조

이 프로젝트의 기본 파일 구조입니다. src/db 디렉토리 안에는 schema.ts 파일에 테이블 정의가 있습니다. drizzle 폴더에는 SQL 마이그레이션 파일과 스냅샷이 있습니다.

📦 <project root>
 ├ 📂 drizzle
 ├ 📂 src
 │   ├ 📂 db
 │   │  └ 📜 schema.ts
 │   └ 📜 index.ts
 ├ 📜 .env
 ├ 📜 drizzle.config.ts
 ├ 📜 package.json
 └ 📜 tsconfig.json

1단계 - 필요한 패키지 설치

npm
yarn
pnpm
bun
npm i drizzle-orm dotenv
npm i -D drizzle-kit wrangler @cloudflare/workers-types

2단계 - wrangler.toml 설정

D1 데이터베이스를 사용하려면 wrangler.toml 파일이 필요합니다. 이 파일은 다음과 같이 구성됩니다:

#:schema node_modules/wrangler/config-schema.json
name = "sqlite-durable-objects"
main = "src/index.ts"
compatibility_date = "2024-11-12"
compatibility_flags = [ "nodejs_compat" ]

# Durable Object 바인딩. Durable Object는 액터 모델을 기반으로 한 스케일-투-제로 컴퓨팅 프리미티브입니다.
# Durable Object는 필요한 만큼 오래 유지될 수 있습니다. 실시간 앱과 같이 장기 실행되는 "서버"가 필요할 때 사용합니다.
# 문서: https://developers.cloudflare.com/workers/wrangler/configuration/#durable-objects
[[durable_objects.bindings]]
name = "MY_DURABLE_OBJECT"
class_name = "MyDurableObject"

# Durable Object 마이그레이션.
# 문서: https://developers.cloudflare.com/workers/wrangler/configuration/#migrations
[[migrations]]
tag = "v1"
new_sqlite_classes = ["MyDurableObject"]

# 다음 단계에서 마이그레이션을 가져올 수 있도록 규칙이 필요합니다.
[[rules]] 
type = "Text"
globs = ["**/*.sql"]
fallthrough = true

3단계 - Drizzle ORM을 데이터베이스에 연결하기

// Drizzle ORM과 Durable Object를 사용하여 데이터베이스 연결 설정
import { drizzle, type DrizzleSqliteDODatabase } from 'drizzle-orm/durable-sqlite';
import { DurableObject } from 'cloudflare:workers'

export class MyDurableObject extends DurableObject {
	storage: DurableObjectStorage;
	db: DrizzleSqliteDODatabase;

	constructor(ctx: DurableObjectState, env: Env) {
		super(ctx, env);
		this.storage = ctx.storage;
		this.db = drizzle(this.storage, { logger: false });
	}
}

4단계 - Wrangler 타입 생성하기

npm
yarn
pnpm
bun
npx wrangler types

이 명령어를 실행하면 worker-configuration.d.ts 파일이 생성됩니다.

5단계 - 테이블 생성

src/db 디렉토리에 schema.ts 파일을 생성하고 테이블을 선언해 보겠습니다. 아래 코드는 users_table이라는 테이블을 정의하며, 각 컬럼의 타입과 제약 조건을 설정합니다.

import { int, sqliteTable, text } from "drizzle-orm/sqlite-core";

export const usersTable = sqliteTable("users_table", {
  id: int().primaryKey({ autoIncrement: true }), // 자동 증가하는 기본 키
  name: text().notNull(),                        // 이름 (필수 값)
  age: int().notNull(),                          // 나이 (필수 값)
  email: text().notNull().unique(),              // 이메일 (필수 값, 고유 값)
});

이 코드는 drizzle-orm 라이브러리를 사용하여 SQLite 데이터베이스의 테이블 스키마를 정의합니다. 각 컬럼은 타입과 제약 조건을 가지며, id는 자동 증가하는 기본 키로 설정됩니다.

6단계 - Drizzle 설정 파일 설정하기

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

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

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

export default defineConfig({
  out: './drizzle',
  schema: './src/db/schema.ts',
  dialect: 'sqlite',
  driver: 'durable-sqlite',
});

7단계 - 데이터베이스에 변경 사항 적용하기

마이그레이션 생성하기:

npx drizzle-kit generate

Cloudflare Workers에서만 마이그레이션을 적용할 수 있습니다. 이를 위해 MyDurableObject에 마이그레이션 기능을 정의해 보겠습니다:

// 
import { drizzle, type DrizzleSqliteDODatabase } from 'drizzle-orm/durable-sqlite';
import { DurableObject } from 'cloudflare:workers'
import { migrate } from 'drizzle-orm/durable-sqlite/migrator';
import migrations from '../drizzle/migrations';

export class MyDurableObject extends DurableObject {
	storage: DurableObjectStorage;
	db: DrizzleSqliteDODatabase;

	constructor(ctx: DurableObjectState, env: Env) {
		super(ctx, env);
		this.storage = ctx.storage;
		this.db = drizzle(this.storage, { logger: false });
	}

    async migrate() {
        migrate(this.db, migrations);
    }
}

8단계 - 데이터베이스 마이그레이션 및 쿼리 실행

import { drizzle, DrizzleSqliteDODatabase } from 'drizzle-orm/durable-sqlite';
import { DurableObject } from 'cloudflare:workers';
import { migrate } from 'drizzle-orm/durable-sqlite/migrator';
import migrations from '../drizzle/migrations';
import { usersTable } from './db/schema';

export class MyDurableObject extends DurableObject {
    storage: DurableObjectStorage;
    db: DrizzleSqliteDODatabase;

    constructor(ctx: DurableObjectState, env: Env) {
        super(ctx, env);
        this.storage = ctx.storage;
        this.db = drizzle(this.storage, { logger: false });
    }

    async migrate() {
        await migrate(this.db, migrations);
    }

    async insert(user: typeof usersTable.$inferInsert) {
        await this.db.insert(usersTable).values(user);
    }

    async select() {
        return this.db.select().from(usersTable);
    }
}

export default {
    /**
     * Cloudflare Worker의 표준 fetch 핸들러입니다.
     *
     * @param request - 클라이언트로부터 Worker에 제출된 요청
     * @param env - wrangler.toml에 선언된 바인딩을 참조하기 위한 인터페이스
     * @param ctx - Worker의 실행 컨텍스트
     * @returns 클라이언트에게 반환될 응답
     */
    async fetch(request: Request, env: Env): Promise<Response> {
        const id: DurableObjectId = env.MY_DURABLE_OBJECT.idFromName('durable-object');
        const stub = env.MY_DURABLE_OBJECT.get(id);
        await stub.migrate();

        await stub.insert({
            name: 'John',
            age: 30,
            email: 'john@example.com',
        });
        console.log('새로운 사용자가 생성되었습니다!');

        const users = await stub.select();
        console.log('데이터베이스에서 모든 사용자를 가져옵니다: ', users);

        return new Response();
    }
}