SvelteKit × Cloudflare D1にDrizzle ORMを導入した話

Drizzle ORMはTypeScript向けの軽量ORMです。platform.env.DB.prepare('SELECT ...') と書いていた生SQLを、型安全なTypeScriptのコードに置き換えられます。今回は既存のCloudflare D1テーブルに対してDrizzleを導入しました。


Drizzleのメリット

生SQL Drizzle
型安全 ❌ 手動で型を書く必要がある ✅ スキーマから自動生成
タイポ ❌ 実行時エラー ✅ コンパイル時エラー
リレーション 手動でJOINを書く .with() で簡潔に書ける
マイグレーション 手動でSQLを書く drizzle-kit で自動生成

セットアップ

1. インストール

npm install drizzle-orm
npm install -D drizzle-kit

2. スキーマを定義

src/lib/db/schema.ts を作成します。既存のテーブル構造に合わせて定義します。

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

export const posts = sqliteTable('posts', {
  id: int('id').primaryKey({ autoIncrement: true }),
  title: text('title').notNull(),
  body: text('body').notNull(),
});

export const users = sqliteTable('users', {
  id: text('id').primaryKey(),
  githubId: int('github_id').notNull(),
  name: text('name').notNull(),
});

export const sessions = sqliteTable('sessions', {
  id: text('id').primaryKey(),
  userId: text('user_id').notNull(),
  expiresAt: int('expires_at').notNull(),
});

3. Drizzleクライアントのヘルパーを作成

src/lib/db/index.ts を作成します。

import { drizzle } from 'drizzle-orm/d1';
import * as schema from './schema';

export function createDb(d1: D1Database) {
  return drizzle(d1, { schema });
}

4. drizzle.config.ts を作成

/// <reference types="node" />
import { defineConfig } from 'drizzle-kit';

export default defineConfig({
  schema: './src/lib/db/schema.ts',
  out: './migrations',
  dialect: 'sqlite',
  driver: 'd1-http',
  dbCredentials: {
    accountId: process.env.CLOUDFLARE_ACCOUNT_ID!,
    databaseId: process.env.CLOUDFLARE_DATABASE_ID!,
    token: process.env.CLOUDFLARE_D1_TOKEN!,
  },
});

ポイント:/// <reference types="node" />

tsconfig.json@cloudflare/workers-types が指定されているため、そのままでは process が認識されません。トリプルスラッシュディレクティブをファイル先頭に追加することで、そのファイルだけNode.jsの型を有効にできます。


SvelteKitで使う

クエリの書き方

import { createDb } from '$lib/db';
import { posts } from '$lib/db/schema';
import { eq } from 'drizzle-orm';

const db = createDb(platform!.env.DB);

// SELECT(全件)
const allPosts = await db.select().from(posts);

// SELECT(特定カラムのみ)
const titles = await db.select({ id: posts.id, title: posts.title }).from(posts);

// SELECT(条件付き)
const post = await db.select().from(posts).where(eq(posts.id, 1));

// INSERT
await db.insert(posts).values({ title: 'タイトル', body: '本文' });

// UPDATE
await db.update(posts).set({ title: '新しいタイトル' }).where(eq(posts.id, 1));

// DELETE
await db.delete(posts).where(eq(posts.id, 1));

APIルートへの組み込み例

// src/routes/api/posts/+server.ts
import { json } from '@sveltejs/kit';
import type { RequestHandler } from './$types';
import { createDb } from '$lib/db';
import { posts } from '$lib/db/schema';

export const GET: RequestHandler = async ({ platform }) => {
  const db = createDb(platform!.env.DB);
  const allPosts = await db.select({ id: posts.id, title: posts.title }).from(posts);
  return json(allPosts);
};

export const POST: RequestHandler = async ({ request, platform }) => {
  const body = await request.json() as { title: string; body: string };
  const db = createDb(platform!.env.DB);

  await db.insert(posts).values({
    title: body.title,
    body: body.body,
  });

  return json({ success: true }, { status: 201 });
};

今回やったこととやっていないこと

今回は既存のD1テーブルに対してDrizzleを導入しました。テーブルの再作成やマイグレーションは行っていません。

やったこと やっていないこと
スキーマ定義(既存テーブルに合わせて) drizzle-kit でのマイグレーション生成
Drizzleクライアントの作成 テーブルの新規作成
生SQL → Drizzleクエリへの書き換え リレーションの定義

Drizzleの本来の使い方はスキーマを定義してから drizzle-kit generate でマイグレーションSQLを生成し、テーブルを作成するところから始まります。既存テーブルがある場合は今回のようにスキーマを合わせて定義することでDrizzleを導入できます。


まとめ

  • drizzle-orm + drizzle-kit をインストールしてスキーマを定義するだけで導入できる
  • createDb(platform!.env.DB) でCloudflare D1に接続したDrizzleインスタンスを作れる
  • select / insert / update / delete が型安全に書ける
  • drizzle.config.tsprocess を使う場合は /// <reference types="node" /> が必要
← トップページに戻る