Supabase Edge Functions 完整教學:用 Deno 打造 Serverless 邊緣函數
三個月前,我們的產品需要串接 Stripe webhook 來處理付款通知。傳統做法是開一台 Express server 跑在 EC2 上,但那台機器 99% 的時間都在閒置——我們一天最多幾百筆交易,完全不需要一台 24 小時運行的伺服器。後來我把整個 webhook handler 改成 Supabase Edge Functions,不到 50 行程式碼就搞定了,每月成本從 $15 降到幾乎 $0。
如果你正在用 Supabase 做後端,Edge Functions 是你一定要學的功能。它讓你在不另外開伺服器的情況下,就能跑自訂的 server-side 邏輯。今天我就來完整介紹這個強大的工具。
什麼是 Edge Functions
Supabase Edge Functions 是跑在 Deno Runtime 上的 Serverless 函數,部署在全球各地的邊緣節點(Edge)。你寫一個 TypeScript 函數,Supabase 會幫你部署到離使用者最近的節點上執行。
它的核心特點:
- Deno Runtime:用 TypeScript 開發,原生支援 ES Modules,不需要
node_modules - 全球邊緣部署:自動部署到離使用者最近的節點,延遲極低
- 按需執行:沒有請求就不收費,冷啟動通常在 200ms 以內
- 與 Supabase 深度整合:可以直接存取 Supabase 的資料庫、Auth、Storage
簡單來說,它就是 Supabase 版的 AWS Lambda 或 Cloudflare Workers,但和 Supabase 生態系的整合度更高。如果你需要處理 webhook、發送通知、做資料轉換、串接第三方 API,Edge Functions 是最佳選擇。
環境準備與 Supabase CLI
要開發 Edge Functions,你需要先安裝 Supabase CLI:
# macOS
brew install supabase/tap/supabase
# npm(跨平台)
npm install -g supabase
# 登入你的 Supabase 帳號
supabase login
# 連結到你的專案
supabase link --project-ref your-project-ref
確認 CLI 版本至少是 1.100 以上。舊版的 CLI 可能不支援某些新功能。另外建議安裝 Deno 的 VS Code extension,能獲得更好的開發體驗。
建立第一個 Edge Function
建立一個新的 Edge Function 非常簡單:
# 建立新函數
supabase functions new hello-world
這會在 supabase/functions/hello-world/ 目錄下產生一個 index.ts 檔案:
// supabase/functions/hello-world/index.ts
Deno.serve(async (req: Request) => {
const { name } = await req.json()
const data = {
message: `Hello ${name}!`,
timestamp: new Date().toISOString(),
}
return new Response(
JSON.stringify(data),
{ headers: { "Content-Type": "application/json" } }
)
})
本地測試:
# 啟動本地開發伺服器
supabase start # 如果還沒啟動本地 Supabase
supabase functions serve hello-world
# 測試
curl -i --location --request POST 'http://localhost:54321/functions/v1/hello-world' --header 'Authorization: Bearer YOUR_ANON_KEY' --header 'Content-Type: application/json' --data '{"name": "開發者"}'
注意 Deno.serve 是 Deno 2.x 的標準 API,取代了舊版的 serve() import。如果你看到舊文章用 import { serve } from "https://deno.land/std/http/server.ts",那是過時的寫法。
處理 CORS 跨域請求
如果你的 Edge Function 會被前端直接呼叫,你需要處理 CORS。這是我踩過最多次的坑之一:
// supabase/functions/_shared/cors.ts
export const corsHeaders = {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type',
}
// supabase/functions/my-function/index.ts
import { corsHeaders } from '../_shared/cors.ts'
Deno.serve(async (req: Request) => {
// 處理 preflight 請求
if (req.method === 'OPTIONS') {
return new Response('ok', { headers: corsHeaders })
}
try {
const result = await doSomething()
return new Response(
JSON.stringify(result),
{
headers: { ...corsHeaders, 'Content-Type': 'application/json' },
status: 200,
}
)
} catch (error) {
return new Response(
JSON.stringify({ error: error.message }),
{
headers: { ...corsHeaders, 'Content-Type': 'application/json' },
status: 400,
}
)
}
})
注意 _shared 資料夾的命名:底線開頭的資料夾不會被部署為獨立的 Edge Function,而是作為共用模組。這是 Supabase 的命名慣例。
關於 API 設計的最佳實踐,可以參考 REST API 版本控制策略,裡面有很多實用的建議。
從 Edge Function 存取資料庫
Edge Functions 最大的優勢之一就是能直接存取 Supabase 的資料庫:
import { createClient } from 'https://esm.sh/@supabase/supabase-js@2'
Deno.serve(async (req: Request) => {
// 從環境變數取得 Supabase 連線資訊(自動注入)
const supabaseClient = createClient(
Deno.env.get('SUPABASE_URL') ?? '',
Deno.env.get('SUPABASE_ANON_KEY') ?? '',
{
global: {
headers: { Authorization: req.headers.get('Authorization')! },
},
}
)
// 使用 anon key + RLS 保護的查詢
const { data: publicData, error } = await supabaseClient
.from('articles')
.select('id, title, slug')
.eq('status', 'published')
.order('created_at', { ascending: false })
.limit(10)
// 或用 service role key 繞過 RLS(管理員操作)
const supabaseAdmin = createClient(
Deno.env.get('SUPABASE_URL') ?? '',
Deno.env.get('SUPABASE_SERVICE_ROLE_KEY') ?? ''
)
const { data: allData } = await supabaseAdmin
.from('internal_logs')
.insert({ action: 'function_called', timestamp: new Date() })
return new Response(JSON.stringify({ data: publicData }), {
headers: { 'Content-Type': 'application/json' },
})
})
重要觀念:SUPABASE_URL、SUPABASE_ANON_KEY、SUPABASE_SERVICE_ROLE_KEY 這三個環境變數會自動注入到每個 Edge Function,你不需要手動設定。但其他自訂的環境變數就需要自己管理了。
環境變數與 Secrets 管理
除了自動注入的 Supabase 相關變數,你可以設定自己的 secrets:
# 設定 secret
supabase secrets set STRIPE_SECRET_KEY=sk_live_xxx
supabase secrets set SENDGRID_API_KEY=SG.xxx
# 查看已設定的 secrets
supabase secrets list
# 在 Edge Function 中使用
const stripeKey = Deno.env.get('STRIPE_SECRET_KEY')
# 本地開發時,把 secrets 放在 .env.local
echo "STRIPE_SECRET_KEY=sk_test_xxx" >> supabase/.env.local
記得把 .env.local 加入 .gitignore!千萬不要把 secret keys 推到 Git 上。如果你的部署流程用到 GitHub Actions CI/CD,可以把 secrets 存在 GitHub Secrets 裡。
用 pg_cron 排程觸發
有時候你需要定期執行某些任務——例如每天寄送摘要郵件、每小時清理過期資料。Supabase 內建 pg_cron 擴充套件,可以直接用 SQL 排程觸發 Edge Functions:
-- 先啟用 pg_cron 和 pg_net 擴充套件
create extension if not exists pg_cron;
create extension if not exists pg_net;
-- 每天早上 9 點(UTC)觸發 Edge Function
select cron.schedule(
'daily-digest', -- 排程名稱
'0 9 * * *', -- cron 表達式
$$
select
net.http_post(
url := 'https://your-project.supabase.co/functions/v1/daily-digest',
headers := jsonb_build_object(
'Content-Type', 'application/json',
'Authorization', 'Bearer ' || current_setting('app.settings.service_role_key')
),
body := jsonb_build_object('type', 'daily')
);
$$
);
-- 查看已設定的排程
select * from cron.job;
-- 刪除排程
select cron.unschedule('daily-digest');
這比另外設定外部的 cron 服務方便太多了。所有東西都在 Supabase 裡面,管理起來很集中。
限制與效能考量
Edge Functions 雖然好用,但也有一些限制你需要知道:
- 執行時間限制:免費方案最多 150 秒,Pro 方案最多 400 秒
- 記憶體限制:每個函數最多 256MB(Pro 方案可以申請提高)
- Request body 限制:最大 2MB(如果需要傳大檔案,用 Supabase Storage)
- 冷啟動:第一次呼叫或閒置一段時間後,會有 100-300ms 的冷啟動延遲
- 不支援 WebSocket:如果需要即時通訊,用 Supabase Realtime
- Deno 生態系:某些 Node.js 套件可能不相容,需要找 Deno 版本
效能最佳化建議:
- 減少冷啟動影響:把常用的函數設定為「永遠暖機」(Pro 方案功能)
- 減少 import:只 import 你需要的模組,不要用大型框架
- 使用連線池:如果直接連 PostgreSQL,用
pg_bouncer模式 - 快取策略:對不常變動的資料加上 Cache-Control header
如果你的應用需要容器化部署,可以參考 Docker Compose 的方式來管理更複雜的架構。而認證相關的需求,可以看 JWT 認證機制的最佳實踐。
部署到正式環境
開發完成後,部署只需要一行指令:
# 部署單一函數
supabase functions deploy hello-world
# 部署所有函數
supabase functions deploy
# 部署時指定不需要 JWT 驗證(公開 API)
supabase functions deploy hello-world --no-verify-jwt
# 查看部署狀態
supabase functions list
部署完成後,你的 Edge Function 會在 https://your-project.supabase.co/functions/v1/hello-world 這個 URL 上線。
幾個部署時的注意事項:
- 預設情況下,Edge Functions 需要 JWT token 才能呼叫。如果是公開 API 或 webhook,記得加
--no-verify-jwt - 每次部署都是原子性的——要麼成功,要麼不變,不會出現半部署的狀態
- 部署後大約 30 秒內全球生效
- 你可以同時維護多個版本的函數,只要函數名稱不同就好
我個人的開發流程是:本地用 supabase functions serve 開發測試,確認沒問題後推到 GitHub,然後在 CI/CD pipeline 中自動 supabase functions deploy。這樣可以確保每次部署都經過測試,不會手動部署出問題。
Supabase Edge Functions 是我目前最喜歡的 Serverless 方案之一。它的開發體驗非常好——TypeScript 原生支援、和 Supabase 生態系無縫整合、部署簡單。如果你的專案已經在用 Supabase,沒有理由不用 Edge Functions。從一個簡單的 webhook handler 開始,你會愛上這種開發方式的。
繼續閱讀
API 限流器完整指南:令牌桶與滑動窗口演算法 Node.js 實作教學
相關文章
你可能也喜歡
探索其他領域的精選好文