7
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

CloudFlareにデプロイするためのRemixプロジェクトを作成する(D1にアクセスしてデータを取得・表示する)

Last updated at Posted at 2024-06-12

概要

CloudFlareにデプロイするためのRemixプロジェクトを作成する方法をまとめる。
なお、ローカル開発環境ではPrismaを用いてSQLiteを、CloudFlareでのデプロイ環境ではD1を用いる。

やること

  • CloudFlareにデプロイするRemixプロジェクトを作成
  • CloudFlareへデプロイできるようにする
  • DBはCloudFlareのD1を使う

前提

  • 筆者はMacのローカルのNode.jsのバージョン管理にvoltaを用いている。
  • Macのローカルでnpmコマンドが実行できること。
  • CloudFlareのアカウントはすでに持っておりブラウザからログインする事ができること。
  • CloudFlare上でD1のDBが作成されていること。

方法

Remixプロジェクトの作成

CloudFrontにデプロイするRemixのプロジェクトを作成する場合、作成時にCloudFlareのテンプレートを指定して作成したほうが良いです。
「一旦何もテンプレートは指定せずプロジェクト作ってあとからCloudFlare用の設定をすればいいや〜」はかなり大変です。

  1. 下記を実行して「todo-cloudflare」というRemixのプロジェクトを作成する。(作成中にCloudFlareへのログインを求められる事がある。)

    npm create cloudflare@latest todo-cloudflare-d1 -- --framework=remix
    
  2. プロジェクト作成中の分岐は下記の様に選択する。

    1. Initialize a new git repository?(ローカルGitリポジトリの初期化、および初期ファイルのコミット) → Yes
    2. Install dependencies with npm?(npmの初期化) → Yes
    3. Do you want to deploy your application?(今すぐデプロイするかどうか) → Yes (この前後でログインを求められる事がある。)
  3. 実行が完了するとデプロイされた画面がブラウザで開く。(おそらくURLのパスは同じプロジェクト名に設定しても異なる。)

    CleanShot 2024-06-11 at 00.39.19@2x.png

  4. 下記を実行してプロジェクトのディレクトリ直下のNode.jsのバージョンを20.11.1に固定しておく。

    cd todo-cloudflare-d1
    volta install node@20.11.1
    volta pin node@20.11.1
    

ローカルリポジトリのGitの設定

  1. 下記をそれぞれ実行してGitのコミッターの設定をしておく。

    git config --local user.name '任意のコミッター名'
    git config --local user.email '任意のコミッターのメールアドレス'
    
  2. もしデフォルトブランチがmasterのままならmainに変えておくことをおすすめする。参考記事を下記に記載する。

DBの設定

  1. 下記を実行してPrismaを用意する。

    npm install prisma
    npm install @prisma/client
    npm install @prisma/adapter-d1
    npx prisma init
    
  2. prisma/schema.prismaを下記のように編集する。

    prisma/schema.prisma
    // This is your Prisma schema file,
    // learn more about it in the docs: https://pris.ly/d/prisma-schema
    
    // Looking for ways to speed up your queries, or scale easily with your serverless or edge functions?
    // Try Prisma Accelerate: https://pris.ly/cli/accelerate-init
    
    generator client {
      provider = "prisma-client-js"
      previewFeatures = ["driverAdapters"]
    }
    
    datasource db {
      provider = "sqlite"
      url      = env("DATABASE_URL")
    }
    
    // userテーブルの追加のために下記を追記
    model users {
      id    Int     @id @default(autoincrement())
      email String  @unique
      name  String
    }
    
  3. プロジェクトルートに.envを用意し、下記のように記載する。(ローカル開発環境ではD1のローカル用のDBを使うため不要かも知れないが、設定されていない状態でenv("DATABASE_URL")がどのような挙動になるか不安なので設定しておく。)

    .env
    DATABASE_URL="file:./dev.db"
    
  4. .envの例を作っておく。

    cp .env .env.example
    
  5. プロジェクトルート直下のwrangler.tomlを開きまずは下記の記載をコメントアウトする。

    wrangler.toml
    # [[d1_databases]]
    # binding = "MY_DB"
    # database_name = "my-database"
    # database_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
    
  6. 更に下記の様に記載する。

    wrangler.toml
    [[d1_databases]]
    binding = "RemixのコードからDBにアクセスするときに使う任意の文字列"
    database_name = "CloudFlare上のD1のデータベース名"
    database_id = "CloudFlare上のD1のデータベースID"
    
  7. 下記を実行してマイグレーションのクエリを記載するファイルを作成する。

    npx wrangler d1 migrations create wrangler.tomlに記載したdatabase_name create_users_table
    
  8. 下記を実行してマイグレーションのクエリを追記する。

    npx prisma migrate diff --from-empty --to-schema-datamodel ./prisma/schema.prisma --script --output migrations/0001_create_users_table.sql
    
  9. 下記を実行してローカル開発環境用D1にマイグレーションを実行する。

    npx wrangler d1 migrations apply wrangler.tomlに記載したdatabase_name --local
    
  10. 実行すると下記のようになる。

    $ npx wrangler d1 migrations apply todo-cloudflare-d1 --local
     ⛅️ wrangler 3.57.1 (update available 3.60.2)
    -------------------------------------------------------
    Migrations to be applied:
    ┌─────────────────────────────┐
    │ name                        │
    ├─────────────────────────────┤
    │ 0001_create_users_table.sql │
    └─────────────────────────────┘
    ✔ About to apply 1 migration(s)
    Your database may not be available to serve requests during the migration, continue? … yes
    🌀 Executing on local database todo-cloudflare-d1 (4b6eada0-7ef7-4717-87ad-074b0fd04ccc) from .wrangler/state/v3/d1:
    🌀 To execute on your remote database, add a --remote flag to your wrangler command.
    ┌─────────────────────────────┬────────┐
    │ name                        │ status │
    ├─────────────────────────────┼────────┤
    │ 0001_create_users_table.sql │ ✅       │
    └─────────────────────────────┴────────┘
    
  11. 下記を実行してCloudFlare上のD1にマイグレーションを実行する。

    npx wrangler d1 migrations apply wrangler.tomlに記載したdatabase_name --remote
    
  12. 下記を実行してローカル開発環境用D1にテストデータを格納する。

    npx wrangler d1 execute wrangler.tomlに記載したdatabase_name --command "INSERT INTO  \"users\" (\"email\", \"name\") VALUES('hoge@example.com', 'Foo Bar (Local)');" --local
    
  13. 下記を実行してCloudFlare上のD1にテストデータを格納する。

    npx wrangler d1 execute wrangler.tomlに記載したdatabase_name --command "INSERT INTO  \"users\" (\"email\", \"name\") VALUES('hoge@example.com', 'Foo Bar (Remote)');" --remote
    
  14. ローカル開発環境用D1にTablePlusなどでアクセスする場合、SQLiteの接続設定で.wrangler/state/v3/d1/miniflare-D1DatabaseObjectディレクトリ直下の.sqliteファイルを指定すれば開くことができる。

  15. 下記を実行してPrismaのクライアントを作成する。

    npx prisma generate
    
  16. 下記を実行してwarangler.tomlから型ファイルを生成する。(実行するとプロジェクトルートにworker-configration.d.tsというファイルができる。)

    npx wrangler types
    
  17. 下記を実行してディレクトリとファイルの作成を行う。

    mkdir app/database
    touch app/database/client.ts
    
  18. app/database/client.tsに下記の内容を記載する。

    app/database/client.ts
    import { PrismaClient } from '@prisma/client'
    import { PrismaD1 } from '@prisma/adapter-d1'
    
    export const connection = async (db: D1Database) => {
      const adapter = new PrismaD1(db)
      return new PrismaClient({ adapter })
    }
    
  19. プロジェクトルートのload-context.tsを下記のように編集する。

    load-context.ts
    import { type AppLoadContext } from '@remix-run/cloudflare'
    import { type PlatformProxy } from "wrangler";
    import { connection } from './app/database/client'
    
    type Cloudflare = Omit<PlatformProxy<Env>, "dispose">;
    
    declare module "@remix-run/cloudflare" {
      interface AppLoadContext {
        cloudflare: Cloudflare;
        db: Awaited<ReturnType<typeof connection>>;
      }
    }
    
    type args = {
      request: Request,
      context: {
        cloudflare: Cloudflare
      }
    }
    type GetLoadContext = (args: args) => Promise<AppLoadContext>
    
    export const getLoadContext: GetLoadContext = async ({ context }) => {
      return {
        ...context,
        db: await connection(
          context.cloudflare.env.DB, // 末尾のDBはwrangler.tomlのbindingの値
        ),
      }
    }
    
  20. プロジェクトルートのvite.config.tsを下記のように編集する。

    vite.config.ts
    import {
      vitePlugin as remix,
      cloudflareDevProxyVitePlugin as remixCloudflareDevProxy,
    } from "@remix-run/dev";
    import { defineConfig } from "vite";
    import tsconfigPaths from "vite-tsconfig-paths";
    import { getLoadContext } from "./load-context";
    
    export default defineConfig({
      plugins: [
        remixCloudflareDevProxy({ getLoadContext }),
        remix({
          future: {
            v3_fetcherPersist: true,
            v3_relativeSplatPath: true,
            v3_throwAbortReason: true,
          },
        }),
        tsconfigPaths(),
      ],
    });
    
  21. functions/[[path]].tsを開き下記のように編集する。

    functions/[[path]].ts
    import { createPagesFunctionHandler } from "@remix-run/cloudflare-pages";
    
    // eslint-disable-next-line @typescript-eslint/ban-ts-comment
    // @ts-ignore - the server build file is generated by `remix vite:build`
    // eslint-disable-next-line import/no-unresolved
    import * as build from "../build/server";
    import { getLoadContext } from "../load-context";
    
    export const onRequest = createPagesFunctionHandler({ build, getLoadContext });
    
  22. npm run devを実行して下記のような画面が現段階で問題なく開くことを確認する。

    New_Remix_App.png

  23. app/routes直下にloader.tsを作成し、下記のように記載する。

    app/routes/loader.ts
    import type { LoaderFunctionArgs} from "@remix-run/cloudflare";
    
    export const loader = async ({ context }: LoaderFunctionArgs) => {
      try {
        return await context.db.users.findMany();
      } catch (error) {
        console.error("Failed to load users:", error);
        throw new Response("Internal Server Error", { status: 500 });
      }
    }
    
  24. app/routes/_index.tsxを開き下記のように修正する。

    app/routes/_index.tsx
    import type { MetaFunction } from "@remix-run/cloudflare";
    import { loader } from "./loader";
    export { loader };
    import { useLoaderData } from "@remix-run/react";
    
    export const meta: MetaFunction = () => {
      return [
        { title: "New Remix App" },
        {
          name: "description",
          content: "Welcome to Remix on Cloudflare!",
        },
      ];
    };
    
    interface User {
      id: string;
      name: string;
      email: string;
    }
    
    export default function Index() {
      const users = useLoaderData<User[]>();
      return (
        <div style={{ fontFamily: "system-ui, sans-serif", lineHeight: "1.8" }}>
          <h1>Welcome to Remix on Cloudflare</h1>
          <ul>
            <li>
              <a target="_blank" href="https://remix.run/docs" rel="noreferrer">
                Remix Docs
              </a>
            </li>
            <li>
              <a
                target="_blank"
                href="https://developers.cloudflare.com/pages/framework-guides/deploy-a-remix-site/"
                rel="noreferrer"
              >
                Cloudflare Pages Docs - Remix guide
              </a>
            </li>
          </ul>
          <div>
            <h2>Users</h2>
            <ul>
              {users.map((user: User) => (
                <li key={user.id}>
                  <div>ID: {user.id}</div>
                  <div>NAME: {user.name}</div>
                  <div>EMAIL: {user.email}</div>
                </li>
              ))}
            </ul>
          </div>
        </div>
      );
    }
    
  25. 下記のようにローカルの情報が表示されれば問題ない。

    New_Remix_App.png

  26. また、下記のようにすることでjsonを画面に表示する事ができる。

    app/routes/_index.tsx
    import type { MetaFunction } from "@remix-run/cloudflare";
    import { loader } from "./loader";
    export { loader };
    import { useLoaderData } from "@remix-run/react";
    
    export const meta: MetaFunction = () => {
      return [
        { title: "New Remix App" },
        {
          name: "description",
          content: "Welcome to Remix on Cloudflare!",
        },
      ];
    };
    
    interface User {
      id: string;
      name: string;
      email: string;
    }
    
    export default function Index() {
      const users = useLoaderData<User[]>();
      return (
        <div style={{ fontFamily: "system-ui, sans-serif", lineHeight: "1.8" }}>
          <h1>Welcome to Remix on Cloudflare</h1>
          <ul>
            <li>
              <a target="_blank" href="https://remix.run/docs" rel="noreferrer">
                Remix Docs
              </a>
            </li>
            <li>
              <a
                target="_blank"
                href="https://developers.cloudflare.com/pages/framework-guides/deploy-a-remix-site/"
                rel="noreferrer"
              >
                Cloudflare Pages Docs - Remix guide
              </a>
            </li>
          </ul>
          <div>
            <pre>{JSON.stringify(users, null, 2)}</pre>
          </div>
          <div>
            <h2>Users</h2>
            <ul>
              {users.map((user: User) => (
                <li key={user.id}>
                  <div>ID: {user.id}</div>
                  <div>NAME: {user.name}</div>
                  <div>EMAIL: {user.email}</div>
                </li>
              ))}
            </ul>
          </div>
        </div>
      );
    }
    
  27. 一旦コミットしておく。(この次npm run deployを実行するがこれ自体はコミットされていようがいまいが関係なく今のローカル環境のコードをビルドしてデプロイする。)

  28. 下記を実行してデプロイを行う。(フリープランだと容量が大きいのでデプロイできない可能性がある。CloudFlare上で確認しなくて良いならこれはやらなくていい。)

    npm run deploy
    
  29. 上記コマンドをCloudFlareに5ドル課金してでもデプロイを確認したい方はこちら → Remix CloudFlareにデプロイを行ったら容量系のエラーが出た

  30. CloudFlareでデプロイされているURLにアクセスし、トップ画面を確認し「下記を実行してCloudFlare上のD1にテストデータを格納する。」の手順で格納した(Remote)がついているユーザーが表示されていることを確認する。

    New_Remix_App_と_wrangler_toml_—_todo-cloudflare-d1.png

自分用メモ

  • CloudFlare CLIツールログイン

    npx wrangler login
    
  • CloudFlare CLIツールログアウト

    npx wrangler logout
    

参考文献

7
2
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
7
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?