🔥

Bun+HonoX+D1+Prisma を動かしてみた

2024/04/22に公開

個人開発をやりたいなと思い面白そうな技術スタックをかき集めて動かしてみました

honoxプロジェクト作成

bunx create-hono@latest
  • Target directory は bun-honox-d1-prisma
  • templateはx-basicを選択
  • package managerにbunを選択

D1にDatabaseを作成

bunx wrangler d1 create blog

D1への接続確認

bunx wrangler d1 execute blog --command "SELECT name FROM sqlite_schema WHERE type ='table'"

wrangler.tomlファイルを作成

プロジェクト直下にwrangler.tomlファイルを作成してd1 createコマンド実行後の接続情報を張り付けておく
また、Cloudflare PagesにDeployする場合、wrangler.tomlにpages_build_output_dirが必要なので追加しておく

wrangler.toml
name = "bun-honox-d1-prisma"
pages_build_output_dir = "./dist"
[[d1_databases]]
binding = "DB" # i.e. available in your Worker on env.DB
database_name = "blog"
database_id = "xxxxxxx"

Prismaの設定

インストール

bun add -d prisma
bun add @prisma/client @prisma/adapter-d1

初期化

bunx prisma init --datasource-provider sqlite

.gitignoreに.envを入れろと言われるので入れておく

schema.prismaの設定とスキーマ定義

D1サポートを使うのにpreviewFeatures = ["driverAdapters"]を追加する。
また、テーブルのスキーマ定義も追加する。

prisma/schema.prisma
generator client {
  provider = "prisma-client-js"
  previewFeatures = ["driverAdapters"]
}

datasource db {
  provider = "sqlite"
  url      = env("DATABASE_URL")
}

model Post {
  id          String  @id @default(cuid())
  title       String  
  content     String
}

マイグレーションファイル作成

bunx wrangler d1 migrations create blog create_post_table

migrate実行

bunx prisma migrate diff --from-empty --to-schema-datamodel ./prisma/schema.prisma --script --output migrations/0001_create_post_table.sql

localに対してTable作成

bunx wrangler d1 migrations apply blog --local

remoteに対してTable作成

bunx wrangler d1 migrations apply blog --remote

Prisma Client作成

bunx prisma generate

vite.config.tsの変更

Viteの設定でpluginsの設定を追加する。
また、ssr.external@prisma/client@prisma/adapter-d1を追加する必要がある。

vite.config.ts
import pages from '@hono/vite-cloudflare-pages'
import honox from 'honox/vite'
import adapter from '@hono/vite-dev-server/cloudflare'
import client from 'honox/vite/client'
import { defineConfig } from 'vite'

const baseConfig = {
  ssr: {
    external: [
      '@prisma/client',
      '@prisma/adapter-d1',
    ]
  },
}

export default defineConfig(({ mode }) => {
  if (mode === 'client') {
    return {
      ...baseConfig,
      plugins: [client()]
    }
  } else {
    return {
      ...baseConfig,
      plugins: [
        honox({
          devServer: { adapter }
        }),
        pages()
      ],
    }
  }
})

global.d.tsにD1DatabaseのBindings追加

app/global.d.ts
declare module 'hono' {
  interface Env {
    Variables: {}
    Bindings: {
      DB: D1Database
    }
  }
  interface ContextRenderer {
    (content: string | Promise<string>, head?: Head): Response | Promise<Response>
  }
}

必要最小限のコードを追加する

routes/posts配下に追加

一覧画面

routes/posts/index.tsx
import { createRoute } from "honox/factory";
import { PrismaClient } from "@prisma/client";
import { PrismaD1 } from "@prisma/adapter-d1";

export default createRoute(async (c) => {
  const adapter = new PrismaD1(c.env.DB);
  const prisma = new PrismaClient({ adapter });

  const posts = await prisma.post.findMany();
  return c.render(
    <div>
      <h1>Posts</h1>
      <ul>
        {posts.map((post) => (
          <li>
            <a href={`/posts/${post.id}`}>{post.title}</a>
          </li>
        ))}
      </ul>
      <a href={"/posts/create"}>create new post</a>
    </div>
  );
});

作成画面

routes/posts/create.tsx
import { createRoute } from "honox/factory";
import { PrismaClient } from "@prisma/client";
import { PrismaD1 } from "@prisma/adapter-d1";

export default createRoute((c) => {
  return c.render(
    <div>
      <h1>Create Post</h1>
      <form method="post">
        <label>
          Title
          <input name="title" type="text" />
        </label>
        <label>
          Content
          <textarea name="content" />
        </label>
        <button type="submit">Create</button>
      </form>
    </div>,
    {
      title: "Create Post",
    }
  );
});

export const POST = createRoute(async (c) => {
  const adapter = new PrismaD1(c.env.DB);
  const prisma = new PrismaClient({ adapter });
  const body = await c.req.formData();
  const user = await prisma.post.create({
    data: {
      title: body.get("title")?.toString() ?? "",
      content: body.get("content")?.toString() ?? ""
    },
  });
  return c.redirect("/posts");
});

詳細画面

routes/posts/[id].tsx
import { createRoute } from "honox/factory";
import { PrismaClient } from "@prisma/client";
import { PrismaD1 } from "@prisma/adapter-d1";

export default createRoute(async (c) => {
  const adapter = new PrismaD1(c.env.DB);
  const prisma = new PrismaClient({ adapter });
  const { id } = c.req.param();
  const post = await prisma.post.findUnique({
    where: {
      id: id,
    },
  });
  return c.render(
    <div>
      <h1>{post?.title}</h1>
      <p>{post?.content}</p>
      <a href={"/posts"}>posts</a>
    </div>
  );
});

動かしてみる

bun run dev

デプロイしてみる

bun run deploy

GitHub

https://github.com/itaosan/bun-honox-d1-prisma

参考情報

ssr.externalの設定でハマっていた時に見つけて助かりました。
https://blog.berlysia.net/entry/2024-02-29-honox-og-image
https://github.com/kristianfreeman/cloudflare-d1-prisma-honox-starter

次はsqlcも試してみたいな

Discussion