😉

Cloudflare WorkersのBindingsというやり方

2025/02/03に公開

Cloudflareの開発者プラットフォームには様々なプロダクトがあります。例えば、オブジェクトストレージのR2、SQLデータベースのD1、AIモデルが動くWorkers AIなどです。Cloudflare Workersの中でこれらを使う時に欠かせないやり方が「Bindings」です。Workers特有ですが、Bindingsを使うと、SDKを使って外部サービスとつなぐことが必要なくなります。シークレットを文字列の環境変数で扱うケースが少なくなります。JavaScript/TypeScriptのオブジェクトとして外部サービスを使うことができるDXは快適です。このBindings、知らない人も多いと思うので、紹介します。

AIを12行で実装する

Bindingsの感覚を掴むために、実装をみてみましょう。Workers AIをCloudflare Workersの中から使います。

まずwrangler.jsonでWorkers AIのBindingsをすることを設定します。といっても以下のJSONを足すだけです。AIというBinding名を指定します。

wrangler.json
{
  "ai": {
    "binding": "AI"
  }
}

次にコードです。DeepSeek-R1のとあるモデルにアクセスしてレスポンスを表示してみます。やることは単純ですが、コードは想像より短いはずです。12行です。

src/index.ts
import { Hono } from 'hono'

const app = new Hono()

app.get('/', async (c) => {
  const result = await c.env.AI.run('@cf/deepseek-ai/deepseek-r1-distill-qwen-32b', {
    prompt: 'Hello Worldの起源は?'
  })
  return c.json(result)
})

export default app

先ほどwrangler.jsonでWorkers AIのBindingsをAIとしたので、Honoの場合c.env.AIでAIのオブジェクトにアクセスできます。JavaScript/TypeScriptのオブジェクトなので、AI.runという関数を使って結果を取得することができます。

実行してみます。AIの場合--remoteオプションが必要ですが、簡単に立ち上がります。

wrangler dev --remote src/index.ts

Bindingsを使わない場合

次にBindingsを使わない場合の実装はこんな感じというのを見てみましょう。プロダクトのWeb APIをHTTPクライアントで叩くか、SDKを使うのが想像できます。SDKを使う場合はこんなコードでしょうか。

import { Hono } from 'hono'
import { createAIClient } from 'workers-ai-sdk----'

const app = new Hono()

app.use(async (c, next) => {
  const aiClientSecret = getEnv('secret-for-ai')
  const aiClient = createAIClient({
    secret: aiClientSecret
  })
  c.set('aiClient', aiClient)
  await next()
})

app.get('/', async (c) => {
  await c.var.aiClient.run()
  // ...
})

以下のことをやっています。

  1. SDKのライブラリ(イントールしなくてはいけない!)をインポート
  2. 環境変数(当然、設定しなくてはいけない!)からAI用のシークレットを取得
  3. クライアントを作る
  4. どのハンドラから使えるようにc.setでクライントオブジェクトをセット
  5. ハンドラで使う

Bindingsと比べるとやることが多いです。Bindingsの場合wrangler.jsonの設定さえすれば、いきなり「5」の「ハンドラで使う」をすることができます。

Bindingsで要らなくなるもの

ようはBindingsを使うと以下が必要なくなるということです。

  • シークレット
  • SDK(クライアントライブラリ)のインストール
  • 環境変数を文字列で取得すること
  • アプリ内で使えるようにコンテキストへの登録

仕組み

なんでこんなことができるのかを雑に説明するとこういうことです。

  • Workersの開発、実行環境はユーザーごとに独立している
  • 開発ツールのWranglerがローカルでプロダクトをエミュレートするか、リモートにつなげている
    • D1やKVはローカルのSQLiteに置かれる
    • AIはリモートの環境とつなぐ
  • 各プロダクトへのアクセスはBindingsという変数でfetchハンドラに挿入される
    • wrangler.jsonでKVをKVという名前で登録したらenv.KV(Honoの場合 c.env.KV)でアクセスできる

その他の例

Workers AI以外にもBindingsでアクセス可能になるものはたくさんあります。以下が現在可能なものの一覧です。

  • AI
  • Analytics Engine
  • Assets
  • Browser Rendering
  • D1
  • Dispatcher (Workers for Platforms)
  • Durable Objects
  • Environment Variables
  • Hyperdrive
  • KV
  • mTLS
  • Queues
  • R2
  • Rate Limiting
  • Secrets
  • Service bindings
  • Tail Workers
  • Vectorize

では、Workers AI以外の例も見てましょう。

KV

Key-ValueストアのKVを使ったWorkersの例を掲載します。今回はc.env.KVでアクセスできるようにしています。TypeScriptの型定義をきちんといれてみました。補完が綺麗に効きます。KVで面白いのは、メタデータを入れられる点で、この場合はContent Typeを挿入時に入れて、取り出す時に取得して、レスポンスのContent-Typeヘッダにセットしています。

src/index.ts
type Env = {
  Bindings: {
    KV: KVNamespace
  }
}

const app = new Hono<Env>()

app.put('/image', async (c) => {
  const { file, key } = await c.req.parseBody<{ file: File; key: string }>()
  await c.env.KV.put(key, await file.arrayBuffer(), {
    metadata: {
      contentType: file.type
    }
  })
  return c.redirect('/')
})

app.get('/images/:id', async (c) => {
  const result = await c.env.KV.getWithMetadata<{
    contentType: string
  }>(c.req.param('id'), 'arrayBuffer')

  if (!result.value) {
    return c.notFound()
  }

  return c.body(result.value, 200, {
    'Content-Type': result.metadata?.contentType ?? 'image/jpeg'
  })
})

Bindingsを自分で作る

WorkersのRPCという仕組みを使って自分でBindingsを作ることも可能です。

例えば、足し算をするaddを提供するサービス「Calc」を作ります。

workers/calc/src/index.ts
import { WorkerEntrypoint } from 'cloudflare:workers'

export default class Calc extends WorkerEntrypoint {
  add(a: number, b: number) {
    return a + b
  }
  fetch() {
    return new Response()
  }
}

うまいこと設定すればサーバー側でc.env.CALCから使うことができます。もちろんTypeScriptの型サポートも機能します。

workers/server/src/index.ts
import type Calc from 'calc'
import { Hono } from 'hono'

type Bindings = {
  CALC: Service<Calc>
}

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

app.get('/', async (c) => {
  const result = await c.env.CALC.add(1, 2)
  return c.body(result.toString())
})

export default app

RPCについては以下に記事を書いたので参考にしてください。

https://zenn.dev/yusukebe/articles/f1d5aa529ba873

マーケットプレイス

Bindingsを使った夢のような話があって、ユーザーが作ったBindingsをマーケットプレイスのように他の開発者が使えるように流通させるという案です。そうすると、Cloudflareプロダクトに限らず外部のサービスにアクセスするためのBindingsが生まれそうです。例えば、Slack用のBindingsができるかもしれない。こんなコードです。面白いですよね。

app.post('/hook', async (c) => {
  const { message } = await c.req.parseBody()
  await c.env.SLACK.sendMessage({
    message
  })
  // ...
})

プラットフォーム依存?

Cloudflare Workersでしか使わないわけですから、いわゆるプラットフォーム依存なのですが、それに見合うほどDXはいいと思っています。そもそも、CloudflareのプロダクトがCloudflareに依存していますし!

まとめ

以上、Cloudflare WorkersのBindingsについて紹介しました。非常に強力でユニークな考え方であり実装で、僕がCloudflare Workersが好きな理由の大きな一つがBindingsです。既存のHTTPアクセスやSDKを使ったサービスへの接続が必要なくなるのがとてもいいです。上記で例をあげたKVやWorkers AIの例などは使いやすいのでやってみてください。

参考文献

Workersを作ったKentonがBindingsにまつわる話をブログに書いています。思慮深くてとてもいいです。

https://blog.cloudflare.com/workers-environment-live-object-bindings/

https://blog.cloudflare.com/javascript-native-rpc/

Discussion