株式会社Berry

Supabase Edge Functions 実践ガイド:認証から本番デプロイまで

に公開

初めまして!去年の年末に入社しました、株式会社Berryの中川です。
私はヘルメット治療管理システムの開発・運用に携わっており、「あらゆる人が必要な時に必要な医療を受けられる社会を実現する」ことを目指して日々邁進しております。

以前公開したブログの中で、Supabaseを利用してアプリをローンチする際の注意点や、VitestでRLSをテストするために行っている方法についてご紹介しました。

https://zenn.dev/berry_blog/articles/cfce64da076878
https://zenn.dev/berry_blog/articles/03beda8c668127

今回は、webhooksの処理など外部サービスとの連携の際に触れることが多いSupabaseのEdge Functionsの概要について、公式ドキュメントを参考にしつつ、実際に使ってみて気づいた点を交えながらご紹介できればと思います。

そもそもなぜSupabase Edge Functions?

Supabase Edge Functionsは、サーバーサイドで動作するTypeScript関数であり、グローバルのエッジに分散されます。また、webhooksの処理等サードパーティ連携に使用することができます。
Edge Functionsには、以下のようなメリットがあります:

低遅延での処理

Webhooksのような外部サービスから自身のサーバーへのデータ送信には、タイムアウト制限があることが多く、UX向上のためにも低遅延で処理されるのが望まれます。Edge Functionsはユーザーに近いエッジネットワーク上で動作するため、遅延の少ないAPIや処理を実行できます。

スケーラビリティ

Edge Functionsはサーバーレス関数であり、リクエストが届いた際に起動され、リクエスト量に応じて自動スケーリングされるので、手動による調整の負担を軽減できます。

ただ、長時間の処理や高負荷の処理には向いていないため、大規模なAPIや複雑なデータ処理を行う場合は、自前のバックエンドサーバーを活用する方が適切でしょう。

https://supabase.com/edge-functions

Supabase Edge Functionsのディレクトリ構成と作成

では、早速Edge Functionsを作成してみましょう!
まず、Supabase Edge Functionsのディレクトリ構成ですが、各関数ごとにディレクトリを分け、その下に各関数のindex.tsファイルを配置します。

project-root/
    app/
    supabase/
        functions/
            edge-function-1/
                index.ts
            edge-function-2/
                index.ts
        config.toml
        migrations/
        ...

Supabase CLIを使って、以下のコマンドで新しいEdge Functionsを作成できます:

supabase functions new test-edge-function

実行すると、以下のようなsupabase/functions/test-edge-function/index.tsが生成されます。

...
Deno.serve(async (req) => {
  const { name } = await req.json()
  const data = {
    message: `Hello ${name}!`,
  }

  return new Response(
    JSON.stringify(data),
    { headers: { "Content-Type": "application/json" } },
  )
})
...

このように、Deno.serveResponseRequestオブジェクトにアクセスできます。

https://supabase.com/docs/guides/functions/quickstart

Denoセットアップ

上記のコードでご覧の通り、Supabase Edge FunctionsはDenoをランタイムとして使用しているため、Denoのセットアップが必要です。ただし、他のフォルダーが正常にTypeScriptサーバーを使用できるようにするためには、ワークスペース全体ではなく、supabase/functionsフォルダーのみDenoサーバーに切り替わるように設定する必要があります。

詳しい設定方法は公式サイトに記載されています。
https://supabase.com/docs/guides/functions/local-development

リクエスト処理前に認証を行う

次に、セキュリティー対策としてEdge Functionsの認証方法についてお伝えします。

各Supabase Edge Functionsに対してJWT認証の設定を行うことができ、これはデフォルトで有効になっています。有効な場合、Edge Functionsに送られるリクエストに有効なJWTが含まれていないと、関数自体が起動されません。そのため、誰でもEdge Functionsを叩けてしまうことを防ぐことができます。

ただし、webhooks等外部サービスからのリクエストを処理する場合は、JWT認証を無効化する必要があります。外部サービスからのリクエストでは、JWTの発行が行われないためです。注意点として、無効化すると誰でもEdge Functionsを起動できてしまうため、別の方法で認証を行う必要があります。
例えば、以下は公式サイトに掲載されているStripe Webhook処理のサンプルコードで、署名による認証を行っています:

...
Deno.serve(async (request) => {
  const signature = request.headers.get('Stripe-Signature') 

  // First step is to verify the event. The .text() method must be used as the
  // verification relies on the raw request body rather than the parsed JSON.
  const body = await request.text()
  let receivedEvent
  try {
    receivedEvent = await stripe.webhooks.constructEventAsync(
      body,
      signature!,
      Deno.env.get('STRIPE_WEBHOOK_SIGNING_SECRET')!,
      undefined,
      cryptoProvider
    )
  } catch (err) {
    return new Response(err.message, { status: 400 })
  }
  console.log(`🔔 Event received: ${receivedEvent.id}`)
  return new Response(JSON.stringify({ ok: true }), { status: 200 })
});
...

Stripeがwebhookを送信すると、Stripe-Signatureというヘッダーに署名が含まれます。Supabaseで設定された環境変数STRIPE_WEBHOOK_SIGNING_SECRETを使って生成された署名と一致するかどうかの検証が行われています。ローカルおよびデプロイされた環境での変数設定方法については、以下の「ローカル環境で起動」と「本番にデプロイ」でご確認ください。

なお、webhooks処理でJWT認証を無効化にしていない場合、Edge Functions自体が起動されずログも残らないので、そのような状況ではJWT認証の設定を疑ってみましょう。

https://supabase.com/docs/guides/functions/examples/stripe-webhooks

ローカル環境で起動

上記で作成したtest-edge-functionをローカル環境で起動する場合は、以下のコマンドを実行します:

supabase start
supabase functions serve

環境変数の設定は、同じディレクトリ内に.envファイルを追加することで行えます。その場合、supabase/.gitignoreに.envの追加を忘れないようにしましょう。

JWT認証の無効化および環境変数の読み込みは、以下コマンドで実行できます。

supabase functions serve --no-verify-jwt test-edge-function --env-file ./supabase/functions/test-edge-function/.env

functions serveコマンドにはホットリロード機能があり、ファイルの変更を検知してDenoサーバーを自動で再起動します。

起動したtest-edge-functionにリクエストを送信すると:

curl --request POST 'http://localhost:54321/functions/v1/test-edge-function' \
  --header 'Authorization: Bearer SUPABASE_ANON_KEY' \
  --header 'Content-Type: application/json' \
  --data '{ "name":"Berry" }'

{"message":"Hello Berry!"}が返ってきます。

https://supabase.com/docs/guides/functions/quickstart

本番にデプロイ

ローカルでEdge Functionsの作成ができたら、本番にデプロイします:

supabase functions deploy test-edge-function

Supabase CLIのバージョンによっては、デプロイ時にエラーが発生することがあるため、ローカルのバージョンに依存しないよう、package.jsonにデプロイのコマンドのエイリアスを書いておくことをおすすめします:

JWTの無効化及び環境変数の設定は以下のコマンドで実現できます:

supabase functions deploy --no-verify-jwt test-edge-function 
supabase secrets set --env-file ./supabase/functions/test-edge-function/.env

Supabase dashboard上では、以下から環境設定を行えます:

JWT認証の設定変更は以下から:

デプロイされたEdge FunctionsのエンドポイントURL取得はこちら:

外部サービスにエンドポイントURLを設定すると、そのリクエストが該当のEdge Functionに送信されます。

https://supabase.com/docs/guides/functions/deploy

Github Actionsで自動化

Supabase dashboard上では、Edge Functionsの最新更新タイミングとデプロイした回数を確認できます。

ただし、実際にデプロイされた内容の確認はできないようなので、手動デプロイによる内容の乖離を防ぐためにも、Github Actionsでの自動デプロイをおすすめします。
また、Edge FunctionsはデフォルトでJWT認証が有効な状態でデプロイされるため、認証を無効にしたい関数には、デプロイ時に--no-verify-jwtフラグを付けるのを忘れないようにしましょう。

デプロイされたEdge Functionをローカルで確認した場合は、以下でダウンロードします:
supabase functions download

💡補足 (2025年4月1日アップデート)
Supabase Dashboard上でEdge Functionsのコードを直接編集・デプロイできる機能が追加されました。
詳しくは公式ブログをご覧ください:
https://supabase.com/blog/supabase-edge-functions-deploy-dashboard-deno-2-1

まとめ

以上、Supabase Edge Functionsについてざっくりご紹介させていただきました。
Edge Functionsを使えば、手軽に低レイテンシなアプリが構築できますので、ぜひ実感してみてください!

応募待ってます!

WEBエンジニア募集中です!医療業界での経験や3Dの知見は問いません。Berryの考え方や製品に少しでも興味が持てた方はお気軽に応募下さい。

https://www.wantedly.com/projects/1950414

株式会社Berry
株式会社Berry

Discussion