3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Cloudflare×Next.js×D1×Drizzleでローカルと本番を切り替える環境構築の方法

Posted at

はじめに

Next.jsでCloudflareを利用する際にD1をローカルと本番で切り替えるのに手間取ったのでまとめていきます。

環境構築手順

まずはNext.jsの環境構築を行います。
私が試したところ最新のNext.jsでは動かないところがあったので14で行います。

npx create-next-app@14

✔ What is your project named? … my-app
✔ Would you like to use TypeScript? … No / Yes
✔ Would you like to use ESLint? … No / Yes
✔ Would you like to use Tailwind CSS? … No / Yes
✔ Would you like to use `src/` directory? … No / Yes
✔ Would you like to use App Router? (recommended) … No / Yes
✔ Would you like to customize the default import alias (@/*)? … No / Yes

次にCloudflareのドキュメントどおりに設定をしていきます。

cd my-app
npm i -D @cloudflare/next-on-pages
touch wrangler.toml
wrangler.toml
name = "my-app"
compatibility_date = "2024-09-23"
compatibility_flags = ["nodejs_compat"]
pages_build_output_dir = ".vercel/output/static"
next.config.mjs
 import { setupDevPlatform } from '@cloudflare/next-on-pages/next-dev';

/** @type {import('next').NextConfig} */
const nextConfig = {};

 if (process.env.NODE_ENV === 'development') {
   await setupDevPlatform();
 }

export default nextConfig;
package.json
{
  "name": "my-app",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start",
    "lint": "next lint",
    "pages:build": "npx @cloudflare/next-on-pages",
    "preview": "npm run pages:build && wrangler pages dev", // 追加
    "deploy": "npm run pages:build && wrangler pages deploy" // 追加
  },
  "dependencies": {
    "next": "14.2.31",
    "react": "^18",
    "react-dom": "^18"
  },
  "devDependencies": {
    "@cloudflare/next-on-pages": "^1.13.13",
    "@types/node": "^20",
    "@types/react": "^18",
    "@types/react-dom": "^18",
    "eslint": "^8",
    "eslint-config-next": "14.2.31",
    "postcss": "^8",
    "tailwindcss": "^3.4.1",
    "typescript": "^5"
  }
}

デプロイを試してみます

npm run deploy

✔ The project you specified does not exist: "my-app". Would you like to create it? › Create a new project
✔ Enter the production branch name: … main

表示されたURLにアクセスして以下の画面がでればOKです

image.png

次にD1のデータベースを作成していきます。

$ npx wrangler@latest d1 create tutorial

[[d1_databases]]
binding = "DB"
database_name = "tutorial"
database_id = "587b89e5-2a1d-4505-9423-43153d815b2d"

データベースが作成できたら接続情報が表示されるのでwrngler.tomlに追加します。

wrangler.toml
name = "my-app"
compatibility_date = "2024-09-23"
compatibility_flags = ["nodejs_compat"]
pages_build_output_dir = ".vercel/output/static"

[[d1_databases]]
binding = "DB"
database_name = "tutorial"
database_id = "587b89e5-2a1d-4505-9423-43153d815b2d"

次にDrizzleの設定をします。

npm install drizzle-orm
npm install -D drizzle-kit
npm install better-sqlite3
touch drizzle.config.ts
mkdir db
touch db/index.ts
touch db/schema.ts
drizzle.config.ts
import { defineConfig } from "drizzle-kit";
import fs from "node:fs";
import path from "node:path";

function getLocalD1DB() {
  try {
    const basePath = path.resolve(".wrangler/state/v3/d1/miniflare-D1DatabaseObject");
    const dbFile = fs
      .readdirSync(basePath, { encoding: "utf-8", recursive: true })
      .find((f) => f.endsWith(".sqlite"));

    if (!dbFile) {
      throw new Error(`.sqlite file not found in ${basePath}`);
    }

    const url = path.resolve(basePath, dbFile);
    return url;
  } catch (err) {
    console.log(`Error  ${err}`);
  }
}

export default defineConfig({
  dialect: "sqlite",
  schema: "./db/schema.ts",
  out: "./drizzle",
  ...(process.env.NODE_ENV === "production"
    ? {
        driver: "d1-http",
        dbCredentials: {
          accountId: process.env.CLOUDFLARE_D1_ACCOUNT_ID,
          databaseId: process.env.DATABASE,
          token: process.env.CLOUDFLARE_D1_API_TOKEN,
        },
      }
    : {
        dbCredentials: {
          url: getLocalD1DB(),
        },
      }),
});
db/index.ts
import { drizzle } from "drizzle-orm/d1";

import * as schema from "./schema";

// D1Databaseの型定義
declare global {
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  interface D1Database {
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    prepare: (query: string) => any;
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    exec: (query: string) => any;
  }
}

// Cloudflare Pages環境用のD1データベース接続関数
export function createDB(d1: D1Database) {
  return drizzle(d1, { schema, logger: true });
}

// 開発環境用(必要に応じて)
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export const db = drizzle(process.env.DB as any, { schema, logger: true });
db/schema.ts
import { sqliteTable, text } from "drizzle-orm/sqlite-core";

export const users = sqliteTable("user", {
  id: text("id")
    .primaryKey()
    .$defaultFn(() => crypto.randomUUID()),
  name: text("name"),
});

Drizzleのマイグレーションを行うためのコマンドを追加します

package.json
{
  "name": "my-app",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start",
    "lint": "next lint",
    "pages:build": "npx @cloudflare/next-on-pages",
    "preview": "npm run pages:build && wrangler pages dev",
    "deploy": "npm run pages:build && wrangler pages deploy",
    "db:generate": "drizzle-kit generate",
    "db:migrate:dev": "drizzle-kit migrate", // 追加
    "db:migrate:prod": "NODE_ENV=production drizzle-kit migrate" // 追加
  },
  "dependencies": {
    "drizzle-orm": "^0.44.4",
    "next": "14.2.31",
    "react": "^18",
    "react-dom": "^18"
  },
  "devDependencies": {
    "@cloudflare/next-on-pages": "^1.13.13",
    "@types/node": "^20",
    "@types/react": "^18",
    "@types/react-dom": "^18",
    "drizzle-kit": "^0.31.4",
    "eslint": "^8",
    "eslint-config-next": "14.2.31",
    "postcss": "^8",
    "tailwindcss": "^3.4.1",
    "typescript": "^5"
  }
}

DrizzleはDBに接続するので接続情報を.envに追加しましょう

touch .env
.env
CLOUDFLARE_ACCOUNT_ID=あなたのアカウントID
CLOUDFLARE_DATABASE_ID=あなたのデータベースID
CLOUDFLARE_D1_TOKEN=あなたのD1トークン
preview_database_id = "DB"

それぞれの値は以下の記事を参考に埋めてください

それではマイグレーションを行ってみましょう
まずはローカルから行ってみます

npm run db:generate
npm run db:migrate:dev

次はテストデータをローカルのD1に追加します

npx wrangler d1 execute tutorial --local --command "INSERT INTO user (id, name) VALUES ('test-user-1', '田中太郎'), ('test-user-2', '佐藤花子'), 

npx wrangler d1 execute tutorial --local --command "SELECT * FROM user"

ではAPIでDrizzleを使ってローカルのD1から実際にデータを取得できるか確かめます

mkdir -p app/api/users
touch app/api/users/route.ts
route.ts

import { createDB } from "@/db";
import { users } from "@/db/schema";
import { NextResponse } from "next/server";

export const runtime = "edge";

export async function GET() {
  try {
    // Cloudflare Pages環境からD1データベースを取得
    const env = process.env.DB as unknown as D1Database;
    if (!env) {
      return NextResponse.json({ error: "Database not available" }, { status: 500 });
    }
    
    const db = createDB(env);
    const res = await db.select().from(users);
    return NextResponse.json(res);
  } catch (error) {
    console.error("Error fetching users:", error);
    return NextResponse.json({ error: "Failed to fetch users" }, { status: 500 });
  }
}
curl localhost:8788/api/users
[{"id":"test-user-1","name":"田中太郎"},{"id":"test-user-2","name":"佐藤花子"},{"id":"test-user-3","name":"鈴木一郎"}]

ローカルのD1に接続確認ができたので、次は本番でもできるかを確認します
本番にデータを追加しておきます。

npm run db:migrate:prod

npx wrangler d1 execute tutorial --remote --command "INSERT INTO user (id, name) VALUES ('test-user-1', 'watanabe')"

npx wrangler d1 execute tutorial --remote --command "SELECT * FROM user"

データが追加できたのでデプロイしてAPIを叩いてみます

npm run deploy
curl https://1b8e3b32.my-app-5h7.pages.dev/api/users
[{"id":"test-user-1","name":"watanabe"}]

URLはデプロイ先のURLに各自変えてください。
ちゃんと返ってきたのでうまく切り替えができていそうです。

おわりに

サンプルのリポジトリをおいておきます

3
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?