EdgeFunction関連
ローカル開発では再起動と関数実行をひたすら繰り返すので、コマンドライン主体で修正サイクルを回すと早いです。
起動
起動
supabase functions serve
Background起動
supabase functions serve &
ログを書き出しながらバックグラウンド起動
supabase functions serve > functions.log 2>&1 &
バックグラウンドで起動したときの終了方法(fg + Ctrl-C以外)
pkill -f "supabase functions serve"
関数名を指定して起動。ただ今のところ関数をすべて起動してもそれほど重くなさそうなので一括で起動することが多いです。
supabase function serve somefunction
jwt認証を無効化して起動
supabase functions serve --no-verify-jwt
関数ごとにjwt認証無効など
config.toml
に関数ごとの設定の既定値を書いておけます。
[functions.somefunction]
verify_jwt = false
こうすると supabase function serve
したり supabase function deploy
したときに関数ごとに設定を個別に行うことができます。
ポート番号変更
これも設定。複数プロジェクトをローカルで同時に動かすときには10ずつずらしています。
[api]
enabled = true
# Port to use for the API URL.
port = 54411
アクセストークンを取得(jq利用)
ローカルでは 「--no-verify-jwt すれば開発が捗る」とはいえ、RLSが有効になっているテーブルに対してEdgeFunctionを動作させる場合にはユーザーの認証情報を引き継いで検証しながら開発をする必要があります。
まずは存在するユーザーでJWTトークンを取得。
ポート番号を変えたりしているので supabase status で確認しながら取得。
認証するユーザーとパスワードは変数で定義。
USER=user1@kater.jp PASSWORD=password123
curl -s -X POST $(supabase status --output json | jq -r '.API_URL')'/auth/v1/token?grant_type=password' \
-H "apikey: $(supabase status --output json | jq -r '.ANON_KEY')" \
-H "Content-Type: application/json" \
-d '{"email":"'${USER}'","password":"'${PASSWORD}'"}' | jq -r '.access_token'
JWTを持ってEgdeFunctionを叩く
curl -H "Authorization: Bearer $ACCESS_TOKEN" $(supabase status --output json | jq -r '.API_URL')/functions/v1/somefunction
まとめて実行
USER=user1@kater.jp PASSWORD=password123
ACCCESS_TOKEN=$(curl -s -X POST $(supabase status --output json | jq -r '.API_URL')'/auth/v1/token?grant_type=password' \
-H "apikey: $(supabase status --output json | jq -r '.ANON_KEY')" \
-H "Content-Type: application/json" \
-d '{"email":"'${USER}'","password":"'${PASSWORD}'"}' | jq -r '.access_token')
curl -H "Authorization: Bearer $ACCESS_TOKEN" $(supabase status --output json | jq -r '.API_URL')/functions/v1/somefunction
関数の再起動とまとめて実行
# 再起動
pkill -f "supabase functions serve" ; supabase functions serve > supabase/functions.log 2>&1 & sleep 2
# JWTの取得
USER=user1@kater.jp PASSWORD=password123
ACCCESS_TOKEN=$(curl -s -X POST $(supabase status --output json | jq -r '.API_URL')'/auth/v1/token?grant_type=password' \
-H "apikey: $(supabase status --output json | jq -r '.ANON_KEY')" \
-H "Content-Type: application/json" \
-d '{"email":"'${USER}'","password":"'${PASSWORD}'"}' | jq -r '.access_token')
# 関数を叩く
curl -H "Authorization: Bearer $ACCESS_TOKEN" $(supabase status --output json | jq -r '.API_URL')/functions/v1/somefunction
EdgeFunction内でユーザーをトークンのユーザに切り替え
const supabaseClient = createClient(
supabaseUrl ?? '', supabaseAnonKey ?? '',
{
global: {
headers: { Authorization: 'Bearer ' + req.headers.get('Authorization') || '' },
},
}
)
// ユーザーをANONからJWTのユーザーにすげ替える
const token = authHeader.replace('Bearer ', '')
const { data: { user }, error: authError } = await supabaseClient.auth.getUser(token)
下記の公式の記事を見ていたんですがうまくいかず。
で、公式サンプル とかを見ると createClient
のときの第三引数に global.headers
を与える必要があるっぽくて、それを与えるとうまくいきました。
下記です。
createClient(
supabaseUrl ?? '', supabaseAnonKey ?? '',
{
global: {
headers: { Authorization: 'Bearer ' + req.headers.get('Authorization') || '' },
},
}
)
DenoじゃなくてNodeで検証
EdgeFunctionの起動すら面倒なときはまずNodeで検証して移植すると良いです。でもDeno特有の問題とかあるので注意が必要。
Nodeで検証するサンプルコードと検証コマンド
import { createClient } from '@supabase/supabase-js';
const SUPABASE_URL = 'http://127.0.0.1:54411';
const SUPABASE_ANON_KEY = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6ImFub24iLCJleHAiOjE5ODM4MTI5OTZ9.CRXP1A7WOeoJeXxjNni43kdQwgnWNReilDMblYTn_I0';
async function main(jwt: string) {
console.log('Received JWT:', jwt);
// Supabaseクライアントの作成
const supabase = createClient(SUPABASE_URL, SUPABASE_ANON_KEY,
{
global: {
headers: { Authorization: 'Bearer ' + jwt },
},
}
);
try {
// ユーザー認証
// const { data: user, error: authError } = await supabase.auth.getUser(jwt)
const { data: user, error: authError } = await supabase.auth.getUser(jwt);
console.log('認証成功:', user?.user?.email);
// 画像の取得
const { data: imageData, error: imageError } = await supabase
.from('images')
.select('*')
.eq('id', '0287da2f-4058-891c-787e-7d68a7346ee8')
.single();
if (imageError) {
throw imageError;
}
if (!imageData) {
console.log('画像が見つかりませんでした');
return;
}
console.log('取得した画像データ:', imageData);
} catch (error) {
console.error('エラーが発生しました:', error);
}
}
// JWTを引数として渡して実行
if (process.argv.length < 3) {
console.error('使用方法: ts-node get_image2.ts <JWT>');
process.exit(1);
}
main(process.argv[2]);
USER=user1@kater.jp PASSWORD=password123
ACCCESS_TOKEN=$(curl -s -X POST $(supabase status --output json | jq -r '.API_URL')'/auth/v1/token?grant_type=password' \
-H "apikey: $(supabase status --output json | jq -r '.ANON_KEY')" \
-H "Content-Type: application/json" \
-d '{"email":"'${USER}'","password":"'${PASSWORD}'"}' | jq -r '.access_token')
# 関数実行
ts-node sample.ts $ACCESS_TOKEN
参考
DB関連
接続
psqlで接続。ポート番号とか変えてる事があるので supabase status を利用。
psql "$(supabase status --output json | jq -r '.DB_URL')
db reset マイグレーションからの再適応
supabase db reset
db リセットせずにシーディングのみやり直し
db reset
だと時間がかかるので開発用のシードの調整を行う場合には truncate -> シード読み込み で時間短縮できます。
curl -s https://gist.githubusercontent.com/yousan/a29149ffe7ba7459011a61cb9710674b/raw/06d03f4fd4a9593a719a4acffff92545ee6e0fab/truncate_all_data_at_public_schema.sql | psql "$(supabase status --output json | jq -r '.DB_URL')"
psql "$(supabase status --output json | jq -r '.DB_URL')" < supabase/seed.sql ;
上記で使われるSQLのないy
DO $$
DECLARE
r RECORD;
BEGIN
FOR r IN (SELECT tablename FROM pg_tables WHERE schemaname = 'public') LOOP
EXECUTE 'TRUNCATE TABLE public.' || quote_ident(r.tablename) || ' CASCADE';
END LOOP;
END $$;
複数のプロジェクトで使うのでGistに上げておきました。
このスクリプトを使ってDBのクリア。
Storageとマイグレーション
バケット作成
Supabaseをローカル開発しているとStorageの管理もマイグレーションで管理したくなります。
実際にAIなどを使うとマイグレーションでStorageを作成してくれます。
やり方は storage.buckets
テーブルにINSERTをする、という方法です。
INSERT INTO storage.buckets (id, name, public, file_size_limit, allowed_mime_types)
VALUES (
'original-images',
'original-images',
false, -- private bucket
52428800, -- 50MB limit
ARRAY['image/png', 'image/jpeg']::text[]
);
こうしてみるとストレージの実体は storage.buckets
の行であるようです。
バケット名の制約
この場合、INESRTではいろんな文字が通るようですが、コンソールだと通らないことがあります。
自分はアンダーバーを使っていたところ、マイグレーションでは通るがコンソールでは通らない、ということになりました。
マイグレーションだと通ってしまいますが、互換性を考えて大人しくアンダーバーは使わないようにしたほうが良さそうです。
バケットのRLS
ただ気をつけなければいけないのは、ローカルとSaaSでは色々と制約などがあり違うということです。
自分が躓いたのは「RLSが有効にできない」というものでした。
下記のSQLはローカルでは通りますがSaaSでは通りません。
ALTER TABLE storage.objects ENABLE ROW LEVEL SECURITY;
そもそもRLSを有効化していなくてもポリシーは設定できるので、マイグレーションではRLSの有効化は明示しなくてよさそうです。
db reset 時に削除されないのでDELETEする
また、 supabse db reset
した際に、SaaSではストレージのバケットは削除されません。ローカルでは削除されます。
そうすると supabase db reset
がローカルでは削除されてマイグレーションで生成されるのに対し、SaaSでは重複エラーになってしまいます。
そのため、リセット時に消したい場合ではマイグレーションの最初のほうに削除を入れておくとよいです。
DELETE FROM storage.buckets WHERE id = 'original-images';
INSERT INTO storage.buckets (id, name, public, file_size_limit, allowed_mime_types)
VALUES (
'original-images',
'original-images',
false, -- private bucket
52428800, -- 50MB limit
ARRAY['image/png', 'image/jpeg']::text[]
);