Open3

Hono RPC & Cloudfrale Pages & Functions & D1 備忘録

knmtknmt

完成品

https://github.com/kanemototomoki/cloudflare-pages-function-d1

パッケージのバージョン一覧

package.json
  "dependencies": {
    "react": "^18.2.0",
    "react-dom": "^18.2.0"
  },
  "devDependencies": {
    "@cloudflare/workers-types": "^4.20230228.0",
    "@types/react": "^18.0.27",
    "@types/react-dom": "^18.0.10",
    "@vitejs/plugin-react-swc": "^3.0.0",
    "better-sqlite3": "^8.0.1",
    "hono": "^3.0.3",
    "typescript": "^4.9.3",
    "vite": "^4.1.0",
    "wrangler": "^2.12.0"
  }

viteで環境構築

npm create vite@latest

パッケージの追加、npm scriptの編集

npm i -D @cloudflare/workers-types hono wrangler
package.json
  "scripts": {
-  "dev": "vite",
+  "dev": "wrangler pages dev --compatibility-date=2023-03-01 --d1=DB --persist -- vite",
    "build": "tsc && vite build",
    "preview": "vite preview"
  },

https://zenn.dev/yusukebe/articles/c86b8bd8d47a53 を参考にした
wrangerの引数については公式を参照

d1を作成する

wrangler d1 create todo-demo

wrangler.tomlが作成される。

[[ d1_databases ]]
binding = "DB" # i.e. available in your Worker on env.DB
database_name = "todo-demo"
database_id = "xxx"

一度 npm run dev を実行しするとローカルのd1が作成される。

# /.wrangler/state/d1/DB.sqlite3が作成される。
npm run dev

ローカルのDBにテーブルの作成とデータの投入を行う。

todo-schema.sql
DROP TABLE IF EXISTS todos;
CREATE TABLE Todos (
  id INTEGER PRIMARY KEY AUTOINCREMENT,
  title TEXT NOT NULL
)
add-todo.sql
INSERT INTO todos (title) VALUES
  ('Task 1'),
  ('Task 2'),
  ('Task 3'),
  ('Task 4'),
  ('Task 5'),
  ('Task 6'),
  ('Task 7'),
  ('Task 8'),
  ('Task 9'),
  ('Task 10');
# テーブル作成
wrangler d1 execute todo-demo --file=./sql/todo-schema.sql --local

# データ投入
wrangler d1 execute todo-demo --file=./sql/add-todo.sql --local
knmtknmt

ローカルで動作確認する

npm run dev で起動する。

[proxy]: 
  VITE v4.1.4  ready in 170 ms


[proxy]:   ➜  Local:   http://localhost:5173/
  ➜  Network: use --host to expose

Automatically determined the proxy port to be 5173.

リバースプロキシにより http://127.0.0.1:5173/api/todos にAPIが生えると思っていたが、workerデフォルトの 8788 からになってた。
そのためdevはcorsを設定する。

App.tsx
import { useEffect, useState } from 'react';
import './App.css';
import { hc } from 'hono/client';
import type { AppType, TodoResponse } from '../functions/api/[[route]]';

function App() {
  const [todos, setTodos] = useState<TodoResponse[]>([]);
  useEffect(() => {
    async function getTodos() {
      const url = import.meta.env.DEV ? 'http://localhost:8788/api' : 'api';
      const client = hc<AppType>(url);
      const res = await client.todos.$get();
      const json = await res.json();
      return json;
    }
    getTodos().then((todos) => {
      console.warn(todos);
      setTodos(todos);
    });
  }, []);

  return (
    <div className='App'>
      {todos.map((todo) => {
        return (
          <p key={todo.id}>
            id: {todo.id} title: {todo.title}
          </p>
        );
      })}
    </div>
  );
}

export default App;
functions/api/[[route]].ts
import type { D1Database } from '@cloudflare/workers-types';
import { handle } from 'hono/cloudflare-pages';
import { Hono } from 'hono';
import { cors } from 'hono/cors';

type Bindings = {
  DB: D1Database;
};

type Todo = {
  title: string;
};

export type TodoResponse = Todo & {
  id: number;
};

const app = new Hono<{ Bindings: Bindings }>();

app.use(
  '*',
  cors({
    origin: '*',
    allowHeaders: ['Content-Type', 'Authorization'],
    allowMethods: ['POST', 'GET', 'OPTIONS'],
    exposeHeaders: ['Content-Length'],
    maxAge: 600,
    credentials: true,
  })
);

const route = app.get('/todos', async (c) => {
  const { results } = await c.env.DB.prepare(
    'SELECT * FROM todos;'
  ).all<TodoResponse>();
  return c.jsonT(results || []);
});

export type AppType = typeof route;
export const onRequest = handle(app, '/api');

Todoが10件表示されればOK

knmtknmt

デプロイする

deploy用のスクリプトを追加する

json
  "scripts": {
    "dev": "wrangler pages dev --persist --d1=DB -- vite",
    "build": "tsc && vite build",
+  "preview": "vite preview",
+  "deploy": "npm run build && wrangler pages publish pages",
  }

テーブル作成とデータ投入をする

# テーブル作成
wrangler d1 execute todo-demo --file=./sql/todo-schema.sql

# データ投入
wrangler d1 execute todo-demo --file=./sql/add-todo.sql

一度デプロイする

npm run deploy

デプロイ後、サイトにアクセスしてみる。
おそらく500エラーになっており、原因はd1とのバインディングをしてないため。

Cloudflare Pagesの設定タブに D1データベースバインディング の項目がある。
変数名を DB, データベースを作成したものにして設定する。

設定後、再度デプロイし500エラーが解消されればOK