Supabase の Edge Function を Cron で定期実行させた備忘録です。
定期実行の記録が残るようにデータベースの更新も行います。
「Supabase の Edge Function で Hello World する覚書」
https://qiita.com/jackalope/items/452ce72555eeed66a6f0
にて Edge Function が動作する事を確認出来た次のステップになります。
試した環境
- Windows11
- Next.js v15.1.6
- Supabase CLI 2.15.8
- Docker 27.4.0
そして通常そのようになってると思うので「JWT認証が有効」という前提にしてます。
Supabase CLI についてはグローバルインストールしていないので npx で呼び出してます。
グローバルインストールしている場合は良しなに読み替えて下さい。
準備
Supabase のダッシュボードから API Settings を開くと見られるこれらの情報が必要です。
Project URL
https://{ProjectID}.supabase.co
の形式になっている URL になります。
Project ID
Project URL は Project ID を含んだ https://{ProjectID}.supabase.co
という形式になっているので、ここからでも抜き出せます。
以降 {ProjectID} と表記します。
Service Role Key
API Settings の service_role
の項目です。
以降 {ServiceRole} と表記します。
ローカル実行用 anon key
npx supabase start
した際に表示される anon key
です。
以降 {LocalAnonKey} と表記します。
環境変数
環境変数が必要ですがシェルには設定しません。
一旦 .env.function
というファイルに記述しておく事にします。
ファイル名に決まりは無いので判りやすければ何でも良いです。
PROJECT_URL="https://{ProjectID}.supabase.co"
SERVICE_ROLE_KEY="{ServiceRole}"
補足
Supabase にデプロイしたら Supabase 内で実行されるので Project URL や Service Role の設定は不要なのでは?
と思うかもしれませんが、Edge Functions はあくまで Deno 実行環境で動作するサーバレス関数
という位置付けなので何も設定しないと DB への接続情報を持たない状態です。
テーブルの用意
Table Editor から作っても良いですが以下の SQL でも作成出来ます。
RLS は設定する必要もないでしょう。
CREATE TABLE public.cron_log (
id BIGSERIAL PRIMARY KEY,
created_at TIMESTAMP NOT NULL DEFAULT now(),
cron_last_run TIMESTAMP,
cron_count BIGINT NOT NULL DEFAULT 0
);
Deno コード入力
それでは定期実行させるコードを作成します。名前は hello-cron としました。
npx supabase functions new hello-cron
このコマンドで supabase/functions/hello-cron/index.ts
が作成されるのでこれを編集します。
日本語のコメントを含める場合は必ず UTF-8 で保存して下さい。
import "jsr:@supabase/functions-js/edge-runtime.d.ts"
import { createClient } from "https://esm.sh/@supabase/supabase-js";
const supabaseUrl = Deno.env.get("PROJECT_URL")!;
const supabaseServiceKey = Deno.env.get("SERVICE_ROLE_KEY")!;
const supabase = createClient(supabaseUrl, supabaseServiceKey);
Deno.serve(async (req) => {
const clientName = req.headers.get("X-Supabase-Client-Name") ?? "";
const isCronCall = clientName === "supabase-cron";
try {
// テーブルに行があるかどうか確認する
const { data: row, error: selectError } = await supabase
.from("cron_log")
.select("*")
.maybeSingle();
if (selectError) {
console.error("Select error:", selectError);
return new Response("Failed to select row", { status: 500 });
}
if (!row) {
// テーブルが空なので作成
const { error: insertError } = await supabase
.from("cron_log")
.insert([{
cron_count: 1,
cron_last_run: new Date().toISOString(),
}]);
if (insertError) {
console.error("Insert error:", insertError);
return new Response("Failed to insert row", { status: 500 });
}
console.log("Inserted initial row for cron_log.");
} else {
// cron_count を +1, cron_last_run を更新
const newCount = (row.cron_count ?? 0) + 1;
const { error: updateError } = await supabase
.from("cron_log")
.update({
cron_count: newCount,
cron_last_run: new Date().toISOString(),
})
.eq("id", row.id)
;
if (updateError) {
console.error("Update error:", updateError);
return new Response("Failed to update row", { status: 500 });
}
console.log(`Updated row. cron_count: ${newCount}`);
}
} catch (err) {
console.error("Unexpected error:", err);
return new Response("Unexpected server error", { status: 500 });
}
// ここまで来たら成功
return new Response(
JSON.stringify({ cronCall: isCronCall, status: "ok" }),
{ status: 200, headers: { "Content-Type": "application/json" } }
);
})
デプロイの前に、データベースの操作が成功するか一応手元で動作確認しておきましょう。
まずサーバーの起動です。
npx supabase functions serve hello-cron --env-file .env.function
ここで先ほど環境変数を記入したファイルが登場します。
コード内で環境変数を参照しているのでその設定になりますが、--env-file
オプションでこのファイルを指定すると環境変数として設定してくれます。
サーバーを起動したら別コンソールから curl 実行します。
curl -X POST http://127.0.0.1:54321/functions/v1/hello-cron -H "Authorization: Bearer {LocalAnonKey}"
正常に動作すれば以下のように表示されます。
{"cronCall":false,"status":"ok"}
cronCall
は Cron による呼び出しかどうかを示すフラグであり、今回は false
になります。
Cron から呼び出された時以外は処理しない、などの用途に使用出来るかなと思いますが今回は使いません。
それでは Supabase のダッシュボードから cron_log
テーブルを確認してみましょう。
上記 curl を実行させる度に cron_last_run
のタイムスタンプが更新され、cron_count
がインクリメントされているのが観察出来ると思います。ただダッシュボードでの見た目の更新は少し時間が掛かる場合もあります。
デプロイ
デプロイする環境変数を設定します。
.env.function
に記述した内容をそのまま supabase secrets set
でセットするだけです。
npx supabase secrets set PROJECT_URL="https://{ProjectID}.supabase.co"
npx supabase secrets set SERVICE_ROLE_KEY="{ServiceRole}"
設定した環境変数は
npx supabase secrets list
で表示出来ますが、設定された値は暗号化されいるので内容までは確認出来ないので注意が必要です。
準備が出来たらデプロイします。
npx supabase functions deploy hello-cron
これでダッシュボードの Edge Functions に登録されました。
一応デプロイしたコードも実行しておきましょう。
先ほどと違い本番用の Anon Key を指定します。ここでは {AnonKey} と表記します。
curl -X POST https://{ProjectID}.supabase.co/functions/v1/hello-cron -H "Authorization: Bearer {AnonKey}"
問題無ければデータベースが更新されている筈です。
Cron を設定する
Supabase のダッシュボードにある Integrations
から Cron
を有効化します。
有効化したら Integrations から Cron を選択し
Jobs タブ内にある Create job
ボタンを押すと Cron 設定画面が開きます。
この時以下のように Type の項目で Supabase Edge Function が選択出来ない状態になっている事がありますがこれは Edge Function の動作に pg_net が必要だからです。
Install pg_net extension
ボタンから pg_net
をインストールしましょう。
schema は extensions のままで大丈夫です。
改めて Create job
の設定に戻ります。
試しに 1分毎で動作するジョブにしてみます。
Function をセットします
ここ大事、この設定により Authorization ヘッダが付与されます。
完成です。
Save するとこのようなジョブが出来ます。Active がオンになっていれば稼働しているので 1分毎に cron_log テーブルが更新される事を観察出来ると思います。
※ 無駄な負荷になるので確認が終わったら Active をオフにしておきましょう。
これで Supabase Edge Function の定期実行を試すことが出来ました。