Cloudflare WorkersとKVとD1をHonoで使ってみる

Cloudflareについては、これまで業務で関わることがなくノータッチできてしまいました。さすがに大体どういうものかは一般教養として知っておかないとまずいと思って調べたときのメモです。

CDNではなく、エッジコンピューティング環境としてのCloudflareにフォーカスしています。

# Cloudflare Workers

エッジで動作するJavaScriptのランタイム環境です。コンセプトはAWSのLambda@Edgeに似ていると思います。

CloudflareにはFreeプランがあり、無料で試用できます。

## Hono + Cloudflare WorkersでWeb APIを作ってみる

最初にCloudflareでアカウントを作成します。

https://www.cloudflare.com/ja-jp/

Honoのチュートリアルに従って、Cloudflare Workers用のHonoプロジェクトを作成します。

https://hono.dev/docs/getting-started/cloudflare-workers

```bash
npm create hono@latest my-app
cd my-app
npm install
```

src/index.tsを編集して、`/`にHTTP GETでアクセスした時にHelloメッセージを応答するようにします。

```typescript
import { Hono } from 'hono'

const app = new Hono()

app.get('/', (c) => {
  return c.text('Hello Hono!')
})

export default app
```

まずはローカル環境で実行します。`npm run dev`を実行することで、Cloudflare Workersの開発用ツールであるwranglerが動いて、ローカルサーバで動作確認ができます。ブラウザから`http://localhost:8787`にアクセスすると`Hello Hono!`メッセージが表示されます。

次にCloudflare上のデプロイします。`npm run deploy`でデプロイができます。初めてのデプロイの時には、ブラウザが起動し、Cloudflareへのログインが求められます。

CloudflareのDashboardページで`Workers & Pages`を選択すると、my-appがデプロイできていることが確認できます。

![workers_entry.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/126781/7ac8da08-177e-b18a-3eaf-f77673204d12.png)

ブラウザから`https://my-app.<Cloudflareのアカウント名>.workers.dev/`にアクセスすることで、デプロイしたWorkerが動作していることを確認できます。

# KV

Cloudflare Workersから利用できるサーバレスなKey-Valueストアサービスです。

KVはCloudflareのFreeプランで利用可能です。

## Hono + Cloudflare WorkersからKVを使ってみる

wranglerコマンドを利用してKVに新たな名前空間を作ります。

```bash
npx wrangler kv:namespace create "MY_KV"
npx wrangler kv:namespace create --preview "MY_KV"
```

CloudflareのDashboardを見て、確かにKVに指定した名前空間ができていることを確認します。

![kv_entry.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/126781/b0f5cfda-e19b-ab85-9885-19122c3e1772.png)

Honoのプロジェクトで、wrangler.tomlにKVのBinding情報を追記します。
idとpreview_idには、Dashboardで書くにしたidとpreview_idを設定します。

```toml
name = "my-app"
main = "src/index.ts"
compatibility_date = "2024-12-28"

kv_namespaces = [
    { binding = "MY_KV", id = "XXXXX", preview_id = "XXXXX" }
]
```

index.tsを下記のように変更します。

```typescript
import { Hono } from 'hono'
import {KVNamespace} from '@cloudflare/workers-types'

type Bindings = {
  MY_KV: KVNamespace
}

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

app.post('/posts', async (c) => {
  const id = Math.random().toString(36).substring(7);
  await c.env.MY_KV.put(id, `This is an article of ${id}`);
  return c.json({
    id: id
  }, 201);
});

app.get('/posts/:id', async (c) => {
  const id = c.req.param('id');
  const value = await c.env.MY_KV.get(id);
  return c.json({
    id: id,
    article: value
  }, 200);
});

app.get('/', (c) => {
  return c.text('Hello Hono!')
})
```

`npm run dev`でローカル実行して、curlコマンドでPOSTしてGETしてみます。
```bash
% curl -X POST http://localhost:8787/posts
{"id":"p6ypkm"}
% curl http://localhost:8787/posts/p6ypkm
{"id":"p6ypkm","article":"This is an article of p6ypkm"}%        
```

`npm run deploy`でデプロイして、同様にcurlコマンドで確認します。
KVを使ったアプリがデプロイできていることがわかります。

```bash
% curl -X POST http://my-app.<Cloudflareのアカウント名>.workers.dev/posts
{"id":"14k6dt"}                    
% curl http://my-app.<Cloudflareのアカウント名>.workers.dev/posts/14k6dt 
{"id":"14k6dt","article":"This is an article of 14k6dt"}
```

CloudflareのDashboardからKVにエントリが追加されていることを確認できます。

![kv_record.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/126781/dfe99632-9211-ac95-4ce2-f40c2f46ee9d.png)

# D1

Cloudflare Workersから利用できるサーバレスな分散SQL DBサービスです。組み込みDBであるSQLiteがベースになっています。

D1はCloudflareのFreeプランで利用可能です。

## Hono + Cloudflare WorkersからD1を使ってみる

wranglerコマンドを利用してデータベースを新規作成します。

```bash
% npx wrangler d1 create my-app-db
```

テーブルを作ります。ローカルとリモートに作ります。リモートに作る場合は`--remote`オプションをつけます。

```bash
npx wrangler d1 execute my-app-db --local --command='CREATE TABLE Users(UserID INT, FirstName TEXT, LastName TEXT)'
npx wrangler d1 execute my-app-db --remote --command='CREATE TABLE Users(UserID INT, FirstName TEXT, LastName TEXT)'
```

Cloudflare Dashboardでテーブルが作成できたことを確認します。

![d1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/126781/645efaf0-331f-f9b5-1512-a07ad6cb5b8a.png)

Honoのプロジェクトで、wrangler.tomlにKVのBinding情報を追記します。
database_idはwranglerコマンドでデータベースを作成した時に出力された値を設定します。

```toml
d1_databases = [
    { binding = "MY_DB", database_name = "my-app-db", database_id = "xxxx" }
]
```

index.tsに下記のコードを追加します。

```typescript
import { Hono } from 'hono'
import {KVNamespace, D1Database} from '@cloudflare/workers-types'

type Bindings = {
  MY_KV: KVNamespace
  MY_DB: D1Database
}

type UserInfo = {
  firstName: string
  lastName: string
}

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

app.post('/users', async (c) => {
  const userInfo: UserInfo = await c.req.json();
  const id = Math.random().toString(36).substring(7);
  console.log(userInfo);
  await c.env.MY_DB.prepare("INSERT INTO Users (UserID, FirstName, LastName) VALUES (?, ?, ?)").bind(id, userInfo.firstName, userInfo.lastName).run();
  return c.json({
    id: id
  }, 201);
});

app.get('/users/:id', async (c) => {
  const id = c.req.param('id');
  const { results } = await c.env.MY_DB.prepare("SELECT * FROM Users WHERE UserID = ?").bind(id).all();
  if(results.length > 0){
    return c.json({
      id: id,
      firstName: results[0].FirstName,
      lastName: results[0].LastName
    }, 200);
  }else{
    return c.json({}, 404);
  }
});
```

`npm run dev`でローカル実行して、curlコマンドでPOSTしてGETしてみます。
```bash
% curl -X POST -H "Content-Type: application/json" -d  '{"firstName":"Nancy","lastName":"Brown"}' http://localhost:8787/users
{"id":"g2j7wq"}
% curl http://localhost:8787/users/g2j7wq
{"id":"g2j7wq","firstName":"Nancy","lastName":"Brown"}
```

`npm run deploy`でデプロイして、同様にcurlコマンドで確認します。
KVを使ったアプリがデプロイできていることがわかります。

```bash
% curl -X POST -H "Content-Type: application/json" -d  '{"firstName":"Nancy","lastName":"Brown"}' https://my-app.<Cloudflareのアカウント名>.workers.dev/users
{"id":"z6hh6i"}
% curl https://my-app.<Cloudflareのアカウント名>.workers.dev/users/z6hh6i                       
{"id":"z6hh6i","firstName":"Nancy","lastName":"Brown"}
```

CloudflareのDashboardからKVにエントリが追加されていることを確認できます。

![d1_record.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/126781/20706a91-79ce-f179-c172-cec7de15ea38.png)

# R2

Cloudflare Workersから利用できるオブジェクトストレージです。

R2はCloudflareのFreeプラインでは利用ができません。

# 後処理

動作確認が終わったらworkerを削除しておきます。

```bash
npx wrangler delete my-app
```


# まとめ

Honoを使って実装したWorkerから、KVやD1が簡単に利用できました。

JavaScriptの実行環境に加えて、オブジェクトストレージ、キーバーリューストア、SQLデータベースが揃っているので、小規模なWebアプリケーションであれば、問題なく実現ができそうです。

コストが安いので、個人開発の場合は、AWSよりも先にCloudflareを検討した方が、良いかもしれません。