Cloudflare Workers + Hono + Prismaでローカル環境構築
概要
タイトルの通りですが、Cloudflare Workers + Hono + Prismaでローカル環境構築を行ってみたいと思います。
Hono
Edgesのための小さくシンプルで超高速なWebフレームワークです。どのJavaScriptランタイムでも動作します:Cloudflare Workers、Fastly Compute、Deno、Bun、Vercel、Netlify、AWS Lambda、Lambda@Edge、Node.js
Prisma
Prismaは、Node.jsとTypeScriptのための次世代のORM (Object-Relational Mapping) です。データベースとのやり取りをより簡単かつ安全にするために設計されています。開発者がデータベーススキーマを定義し、そのスキーマに基づいて自動的に型安全なクライアントAPIを生成します。これにより、SQLやデータベース固有のクエリ言語に依存することなく、データベースとのやり取りが可能になります。
環境構築
ローカルでの開発環境ですが、事前に作成した以下のリポジトリの環境をベースに進めていきたいと思います。
開発環境としてはIDE VSCode
で DevContainer
を使用して開発する前提で進めていきます。
プロジェクト名は hono-prisma-workers-example
として↑のリポジトリから「Use this template」で新規に作成して進めていきます。
次に .devcontainer/Dockerfile
に以下を追加します。
FROM node:20.10.0-bullseye-slim
LABEL maintainer="Slowhand0309"
ARG username=vscode
ARG useruid=1000
ARG usergid=${useruid}
# create hono時のgit cloneでca証明書が必要なので ca-certificates を追加
# パッケージ管理として pnpm を使用する為追加
RUN set -ex \
&& apt-get update \
&& apt-get install -y \
sudo \
ca-certificates \
--no-install-recommends \
# Delete node user with uid=1000 and create vscode user with uid=1000
&& userdel -r node \
&& groupadd --gid ${usergid} ${username} \
&& useradd -s /bin/bash --uid ${useruid} --gid ${usergid} -m ${username} \
&& echo ${username} ALL=\(root\) NOPASSWD:ALL > /etc/sudoers.d/${username} \
&& chmod 0440 /etc/sudoers.d/${username} \
&& npm install -g pnpm
&& rm -rf /var/lib/apt/lists/*
USER ${username}
次に .devcontainer/docker-compose.yml
に ports
を追加しときます。
services:
app:
...
ports:
- "8787:8787" # 追加
一旦ここまででVSCode上で Command + Shift + P
から「Dev Container: Reopen」を選択し、DevContainerを起動します。
早速pnpm create hono@latest .
で新規プロジェクトを作成します。
templateには cloudflare-workers
を選択します。
create-hono version 0.6.3
✔ Using target directory … .
? Which template do you want to use? cloudflare-workers
? Directory not empty. Continue? yes
✔ Cloning the template
? Do you want to install project dependencies? yes
? Which package manager do you want to use? pnpm
✔ Installing project dependencies
🎉 Copied project files
Get started with: cd .
wrangler.toml
の name
にプロジェクト名を設定しコンテナ外からもアクセスできるように以下を追加しときます。
[dev]
ip = "0.0.0.0"
port = 8787
これで pnpm dev
を実施し [http://localhost:8787](http://localhost:8787)
にアクセスし Hello Hono!
が表示されていればOKです。
Would you like to help improve Wrangler by sending usage metrics to Cloudflare?
と聞かれるのを回避する(不必要であればスキップしてください)
毎回 Containerを起動し直す度に↑こちら聞かれるので、初回の1回のみで次からは聞かれないようにしてみたいと思います。
初回聞かれた際に↑の情報は ~/.config/.wrangler/metrics.json
に保存されます。そこで ~/.config/.wrangler
を volume
に bind します。
-
.devcontainer/docker-compose.yml
に以下を追加しますversion: "3.8" volumes: modules_data: wrangler_data: # 追加 services: app: build: . image: slowhand/nodejs container_name: "hono-prisma-workers" volumes: - ..:/usr/src - modules_data:/usr/src/node_modules - wrangler_data:/home/vscode/.config/.wrangler # 追加 command: /bin/sh -c "while sleep 1000; do :; done" working_dir: /usr/src ports: - "8787:8787" - "5555:5555"
-
.devcontainer/postAttach.sh
に以下を追加しますsudo chown -R vscode /home/vscode/.config
これでコンテナ起動しても聞かれるのは初回のみで、次回からは聞かれなくなります。
PostgreSQLを追加
.devcontainer/docker-compose.yml
に以下を追加します。
volumes:
db_data:
services:
db:
image: postgres:16.2
container_name: postgres_pta
ports:
- 5432:5432
volumes:
- db_data:/var/lib/postgresql/data
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
Prismaの設定
コンテナを起動し直して、Prismaのインストール & 設定をやっていきます。
pnpm add -D prisma
pnpm prisma init --datasource-provider postgresql
生成された .env
ファイルの DATABASE_URL
を以下に修正します。
DATABASE_URL="postgresql://postgres:postgres@db:5432/example?schema=public"
次に prisma/schema.prisma
に User
テーブルを追加します。
model User {
id Int @id @default(autoincrement())
email String @unique
name String?
}
ここまででマイグレーションを実行してみます。
$ pnpm prisma migrate dev --name init
Environment variables loaded from .env
Prisma schema loaded from prisma/schema.prisma
Datasource "db": PostgreSQL database "example", schema "public" at "db:5432"
PostgreSQL database example created at db:5432
Applying migration `20240417131428_init`
The following migration(s) have been created and applied from new schema changes:
migrations/
└─ 20240417131428_init/
└─ migration.sql
Your database is now in sync with your schema.
Running generate... (Use --skip-generate to skip the generators)
WARN 2 deprecated subdependencies found: rollup-plugin-inject@3.0.2, sourcemap-codec@1.4.8
Packages: +1
+
Progress: resolved 123, reused 96, downloaded 1, added 1, done
node_modules/.pnpm/@prisma+client@5.12.1_prisma@5.12.1/node_modules/@prisma/client: Runninnode_modules/.pnpm/@prisma+client@5.12.1_prisma@5.12.1/node_modules/@prisma/client: Running postinstall script, done in 18ms
dependencies:
+ @prisma/client 5.12.1
Done in 5s
✔ Generated Prisma Client (v5.12.1) to ./node_modules/.pnpm/@prisma+client@5.12.1_prisma@5
.12.1/node_modules/@prisma/client in 15ms
これで、実際に User
テーブルが作成され、 @prisma/client
パッケージが追加し内部的に prisma generate
が実行されます。
DBのテーブルやデータの確認には別途クライアントツールを使ったり、Prismaが提供しているブラウザで確認できる Prisma Studio
も使えます。
pnpm prisma studio
↑をコンテナ内で実行し、http://localhost:5555 にアクセスすると User
テーブルが作成されているのが分かるかと思います。
Seedデータ登録
まずは必要なパッケージをインストールします。
pnpm add -D ts-node @types/node
次に tsconfig.json
を修正します。
{
"compilerOptions": {
"target": "es2016", // 修正
"module": "commonjs", // 修正
"strict": true,
"lib": ["ESNext"],
"types": ["node", "@cloudflare/workers-types"], // node追加
"jsx": "react-jsx",
"jsxImportSource": "hono/jsx"
}
}
次に prisma/seed.ts
を以下内容で作成します。
import { Prisma, PrismaClient } from "@prisma/client";
const prisma = new PrismaClient();
const userData: Prisma.UserCreateInput[] = [...Array(5)].map((_, i) => ({
name: `User${i + 1}`,
email: `user${i + 1}@example.com`,
}));
const main = async () => {
const result = await prisma.user.createMany({
data: userData,
skipDuplicates: true,
});
console.log({ result });
};
main()
.catch((e) => {
console.error(e);
process.exit(1);
})
.finally(async () => {
await prisma.$disconnect();
});
package.json
に以下を追加します。
"prisma": {
"seed": "ts-node prisma/seed.ts"
}
ここまできたら実際にseedデータを登録してみます。
$ pnpm prisma db seed
Environment variables loaded from .env
Running seed command `ts-node prisma/seed.ts` ...
{ result: { count: 5 } }
🌱 The seed command has been executed.
Prisma Studio
で確認してデータ登録できとけばOKです。
API実装
いよいよAPI実装ですが、Cloudflare WorkersでPrismaを使用する場合、PrismaのEdge機能を利用する事になるのですが、Engine 部分が Cloudflare Workers上で動作しない為リモート上に別途用意し、そこ経由で接続する必要があります。
詳しくは こちら
Prisma Accelerate などの外部サービスを使って接続可能です。が今回はローカルで動作させたい為、外部サービスに接続して〜なんてことはしたくありません。
※ ちなみにそのまま Cloudflare Workers上でPrismaを使おうとすると次のエラーが出ます。
✘ [ERROR] Error: PrismaClient is not configured to run in Cloudflare Workers. In order to run Prisma Client on edge runtime, either:
- Use Prisma Accelerate: https://pris.ly/d/accelerate
- Use Driver Adapters: https://pris.ly/d/driver-adapters
メッセージにもある様に Driver Adapters
を使って試してみたいと思います。
[node-postgres](https://node-postgres.com/)
を使う
Cloudflare の
connect()
(TCP) を使用してデータベースにアクセスします。 Cloudflare Workers とのみ互換性があり、Vercel Edge Functions とは互換性がありません。
-
prisma/schema.prisma
に以下を追加
generator client {
provider = "prisma-client-js"
previewFeatures = ["driverAdapters"] # ← 追加
}
- PrismaClientを再生成
pnpm prisma generate
-
pg
パッケージと Prisma の Driver Adapter をインストール
pnpm add pg @prisma/adapter-pg
pnpm add -D @types/pg
-
wrangler.toml
にnode_compat = true
を追加する
name = "hono-prisma-workers-example"
compatibility_date = "2023-12-01"
node_compat = true # 追加
-
.dev.vars
を以下内容で作成する
DATABASE_URL="postgresql://postgres:postgres@db:5432/example?schema=public"
- APIリクエスト部分の作成
import { PrismaPg } from "@prisma/adapter-pg";
import { PrismaClient } from "@prisma/client";
import { Hono } from "hono";
import { Pool } from "pg";
const app = new Hono();
app.get("/", (c) => {
return c.text("Hello Hono!");
});
app.get("/users", async (c) => {
const connectionString = `${c.env?.DATABASE_URL ?? ""}`;
const pool = new Pool({ connectionString });
const adapter = new PrismaPg(pool);
const prisma = new PrismaClient({ adapter });
const users = await prisma.user.findMany();
return c.json(users);
});
export default app;
ここまで出来たら http://localhost:8787/users にアクセスし、以下の様なレスポンスが返ってきたらOKです。
[
{
"id": 1,
"email": "user1@example.com",
"name": "User1"
},
{
"id": 2,
"email": "user2@example.com",
"name": "User2"
},
{
"id": 3,
"email": "user3@example.com",
"name": "User3"
},
{
"id": 4,
"email": "user4@example.com",
"name": "User4"
},
{
"id": 5,
"email": "user5@example.com",
"name": "User5"
}
]
トラブルシューティング
pnpm create hono
時に以下エラーが発生する。
throw new DegitError(`could not find commit hash for ${repo.ref}`, {
git
がインストールされていない場合インストールして再度実行する。
git
がインストールされている場合、 git ls-remote https://github.com/honojs/starter
を実行してみて以下のエラーが発生する場合、
fatal: unable to access 'https://github.com/honojs/starter': server certificate verification failed. CAfile: none CRLfile: none
SSLの証明証がインストールされていないので、インストールして再度実行する。
sudo apt-get install --reinstall ca-certificates
APIリクエスト時に以下エラーが発生する
✘ [ERROR] A hanging Promise was canceled. This happens when the worker runtime is waiting for a Promise from JavaScript to resolve, but has detected that the Promise cannot possibly ever resolve because all code and events related to the Promise's I/O context have already finished.
src/index.ts
にて app.get
外で PrismaClient
定義した場合に発生したエラーになります。
const connectionString = `${c.env?.DATABASE_URL ?? ""}`;
const pool = new Pool({ connectionString });
const adapter = new PrismaPg(pool);
const prisma = new PrismaClient({ adapter });
const app = new Hono();
app.get("/users", async (c) => {
const users = await prisma.user.findMany();
return c.json(users);
});
きちんとコネクションが終わらず残ってしまっている状態になっているのかもしれません。もしかしたら他に解決策があるかもですが、今回は app.get
内に PrismaClinet
定義を移動させて解放してやるようにしました。
リポジトリ
今回の内容は以下のリポジトリにpushしてます。
Discussion