首页
/ Supabase Edge Functions 实战指南:本地开发、测试客户端与多环境部署

Supabase Edge Functions 实战指南:本地开发、测试客户端与多环境部署

2026-09-06 18:05:00作者:仰钰奇

Supabase Edge Functions 是构建在 Deno 运行时之上的无服务器函数能力,开发者用 TypeScript 编写业务逻辑,通过 Supabase CLI 一键完成本地调试与云端部署。本指南以 examples/edge-functions 示例仓库为依托,完整讲解从环境准备、本地起服务、密钥配置、浏览器测试客户端,到 CLI 手动部署与 GitHub Actions 自动部署的全链路流程,同时结合仓库内数十个真实函数源码与 supabase/config.toml 配置,帮助读者掌握 Edge Functions 的工程化落地方法。

Edge Functions 与示例仓库概览

Supabase Edge Functions 的核心特征在于:

  • 语言与运行时:函数使用 TypeScript 编写,运行在 Deno 运行时之上,天然支持直接 import URL 依赖与 npm:/jsr: 作用域包。
  • 部署方式:通过 Supabase CLI 完成部署与密钥管理,无需自建服务器。
  • HTTP 入口:每个函数导出 fetch 处理器,符合 Deno Deploy 的标准形态。

在示例仓库中,所有函数均存放于 supabase/functions/ 目录(每个子目录名即函数名,例如 browser-with-cors),旁边配套的 import_map.json 集中声明了 oakopenaistripegrammy@supabase/supabase-jspostgrespuppeteerkyselyreact 等第三方依赖映射,供需要 import map 的函数引用。仓库根目录的 supabase/config.toml 则负责描述整个本地项目与每个函数的部署配置。

示例函数全景:按场景分类的代码素材库

示例目录中沉淀了大量可直接借鉴的真实函数,从源码结构看可以大致划分为以下几类,每一类都包含完整可运行的 index.ts 入口文件:

场景 代表函数 关键依赖 / 能力
鉴权与安全 browser-with-corscustom-jwt-validationselect-from-table-with-auth-rls 基于用户的认证、CORS、RLS 穿透查询
邮件与通知 send-email-resendsend-email-smtpauth-hook-react-email-resend Resend、SMTP、React Email 模板
数据库直连 postgres-on-the-edgekysely-postgresdrizzle Postgres 连接池、Kysely/Drizzle ORM
AI / 大模型 openaiopenai-image-generationhuggingface-image-captioningelevenlabs-text-to-speech 文本补全、图像生成、图像理解、语音合成
图像处理与 OG 图 image-manipulationopengraphog-image-with-storage-cdntweet-to-image Canvas 渲染、og_edge、Storage CDN
第三方集成 telegram-botdiscord-botslack-bot-mentionstripe-webhookscloudflare-turnstile 各平台 Bot/Webhook/验证码
基础设施与限流 upstash-redis-counterupstash-redis-ratelimitstreams Redis 计数与速率限制、流式响应
可观测性 sentrysentryfied Sentry 错误上报
工程化进阶 unit-testingmcpwasm-modules Deno 单元测试、MCP Server、WASM 调用
位置 / 基础工具 locationrestful-tasksoak-server 请求头解析、RESTful 任务、Oak Web 框架

这些函数多数配有独立的 README,团队持续更新维护,可作为日常开发时随取随用的代码素材库。

本地开发:从零启动整套本地环境

本地开发的前提是机器上已安装 Docker(守护进程保持运行),并已下载或升级到最新版 Supabase CLI。整体流程如下:

第 1 步:启动本地堆栈

在示例仓库根目录执行:

supabase start

该命令会依据 supabase/config.toml 拉起本地 Postgres(示例中数据库主版本为 15)、API 网关(端口 54321)、本地鉴权等依赖。仓库的 config.toml 中同时定义了三个存储桶 my-bucketvideos 与公开可读的 imagesobjects_path 指向 supabase/buckets/images),供文件上传类函数(如 background-upload-storage)演示使用。

第 2 步:准备本地环境变量文件

仓库提供了本地密钥模板 supabase/.env.local.example,复制为可编辑文件:

cp ./supabase/.env.local.example ./supabase/.env.local

第 3 步:按需填写密钥

.env.local 中以注释分区的方式列出了每个函数所需的密钥,例如:

  • RESEND_API_KEYSEND_EMAIL_HOOK_SECRET:供 auth-hook-react-email-resendsend-email-resend 使用;
  • OPENAI_API_KEY:供 openai 系列使用;
  • DB_HOSTNAMEDB_PASSWORDDB_USERDB_SSL_CERT:供 postgres-on-the-edgekysely-postgres 直连远端数据库使用(模板注释提醒在 Dashboard 的数据库设置页开启连接池并选择 Transaction 模式);
  • IPINFO_TOKEN:供 location 解析客户端 IP 地理位置使用;
  • STRIPE_API_KEYSTRIPE_WEBHOOK_SIGNING_SECRET:供 stripe-webhooks 验签与查询使用;
  • TELEGRAM_BOT_TOKENFUNCTION_SECRET:供 telegram-bot 使用;
  • UPSTASH_REDIS_REST_URLUPSTASH_REDIS_REST_TOKEN:供两个 Upstash Redis 示例使用;
  • SMTP_HOSTNAMESMTP_PORT 等:供 send-email-smtp 使用。

只需填写你要运行的那个函数对应的变量,其余保持占位即可。

第 4 步:本地托管函数服务

在仓库根目录执行:

supabase functions serve --env-file ./supabase/.env.local --no-verify-jwt

--env-file 把本地密钥注入函数运行时(对应 Deno.env.get(...) 读取),--no-verify-jwt 表示本地调试阶段跳过 JWT 校验,方便用 curl 快速验证。此时函数会暴露在 http://localhost:54321/functions/v1/<function-name>

第 5 步:发起测试请求

三种方式任选其一:

  • 使用函数源码注释中的 CURL 示例。例如 browser-with-cors 源码末尾给出了带用户令牌的调用方式:
curl -i --location --request POST 'http://localhost:54321/functions/v1/browser-with-cors' \
  --header 'Authorization: Bearer <USER_ACCESS_TOKEN>' \
  --header 'Content-Type: application/json' \
  --data '{"name":"Functions"}'

该函数在 config.toml 中被声明为 verify_jwt = true,因此生产调用必须携带有效 JWT;select-from-table-with-auth-rls 同属此类——它通过 ctx.supabase 以当前登录用户身份查询 users 表,从而让行级安全(RLS)规则在函数内继续生效。

  • 使用 supabase-js 客户端的 invoke 方法(见下文"从客户端调用"章节)。
  • 使用随仓库附带的浏览器测试客户端 app 进行可视化请求。

从源码理解函数骨架:认证与 CORS 的正确姿势

示例函数大量采用 npm:@supabase/serverwithSupabase 包装器来统一处理鉴权与跨域。以 select-from-table-with-auth-rls 为例,其结构如下:

import { withSupabase } from 'npm:@supabase/server@^1'

console.log(`Function "select-from-table-with-auth-rls" up and running!`)

export default {
  fetch: withSupabase({ auth: 'user' }, async (req, ctx) => {
    try {
      // ctx.supabase runs queries as the authenticated user, so RLS applies.
      // ctx.userClaims holds the verified user identity.
      const { data, error } = await ctx.supabase.from('users').select('*')
      if (error) throw error

      return Response.json({ user: ctx.userClaims, data })
    } catch (error) {
      return Response.json({ error: error.message }, { status: 400 })
    }
  }),
}

其中值得注意的工程要点:

  • auth: 'user' 声明该端点需要登录态,即部署时应开启 verify_jwt = true,与 supabase/config.toml 中该函数条目保持一致;而 browser-with-cors 的注释也明确写着"Authenticated endpoint, so deploy with verify_jwt = true",可见 JWT 开关是函数设计与部署配置必须对齐的一环。
  • withSupabase 会自动处理 CORS 头,这解释了为什么需要浏览器直调(带 Cookie/跨域)的函数只需一个包装器即可。仓库同时在 supabase/functions/_shared/ 提供了 cors.ts 手写 CORS 实现作为低层替代方案,供不使用包装器的自定义函数复制使用。
  • 自定义 JWT 校验:若后端 Token 不是 Supabase 默认签发格式(例如接入 Clerk 等第三方 IdP),supabase/functions/_shared/jwt/ 目录下的 default.tsclerk.tslegacy-jwt.ts 提供了多种解析策略,custom-jwt-validation 演示了如何对接。
  • 本地 Deno 语言服务:多个函数源码顶部注释建议开发者按照 Deno 官方指引配置编辑器语言服务,以获得自动补全与跳转定义能力。

浏览器测试客户端:一套模拟 Postman 的 React 界面

示例仓库在 app 目录内置了一个基于 Create React App(见 app/package.json,使用 Tailwind 与 @supabase/supabase-js)构建的测试客户端,可同时用于本地与线上函数的请求测试。

本地测试

cd app
npm install
npm start

启动后打开本地页面即可测试。需要注意:本地模式下顶部下拉框不生效,无论选择哪个函数,invoke 只会调用 CLI 当前正在 serve 的那个函数。这个下拉列表定义在 app/src/functionsList.js 中,首项 'local: Whatever function is currently served by the CLI' 正是对这一行为的说明。

在页面逻辑 app/src/App.js 中可以看到,界面通过 supabase.functions.invoke(supaFunction, { body }) 发起请求,并在请求区支持 JSON 编辑器输入(默认请求体为 { name: 'world' })。由于部分示例(如 browser-with-cors)要求登录态,界面还内置了基于 supabase.auth.signInWithPassword / signUp 的注册登录表单,确保可以拿到真实用户令牌去调用受保护函数。

测试已部署的函数

切换到线上测试前,需要把客户端指向云端项目:

  1. app 目录内依据模板创建 .env 文件并填入项目 API 配置(URL 与默认 publishable key 来自 Supabase Dashboard 的 API Settings 页面);
  2. 执行 npm installnpm start 启动。

sapp/src/utils/supabaseClient.js 展示了客户端的双环境适配方式:通过 REACT_APP_SUPABASE_URLREACT_APP_SUPABASE_DEFAULT_PUBLISHABLE_KEY 两个环境变量覆盖默认的 http://localhost:54321 与本地方案的匿名 key。这意味着同一套界面既可以在本地直连 CLI,也可以在配置后访问云端函数,本地/线上切换只靠环境变量驱动。

部署到云端:CLI 完整操作链

当函数在本地验证通过后,即可通过 CLI 部署到云端项目。完整流程分为四步:

第 1 步:生成访问令牌并登录

在 Supabase Dashboard 的 Account Tokens 页面点击 "Generate New Token",复制新生成的令牌后执行:

supabase login

按提示粘贴令牌完成登录。

第 2 步:关联云端项目

在仓库根目录执行(将 your-project-ref 替换为真实项目引用 ID):

supabase link --project-ref your-project-ref

第 3 步:设置生产密钥

supabase secrets set --env-file ./supabase/.env.local

该命令把本地 .env.local 中的密钥批量写入云端。README 同时强调:上述做法隐含了一个前提——本地密钥与生产密钥相同;更推荐的做法是为生产单独维护一份 .env 文件,部署时用生产专用文件设置环境变量,避免把本地调试密钥带上生产。上传后可用以下命令核对生效情况并查看 CLI 默认注入的其它环境变量:

supabase secrets list

第 4 步:部署函数

在仓库根目录执行:

supabase functions deploy your-function-name

部署完成后,记得回到前端测试客户端,从 .env 中移除本地专用的 SUPA_FUNCTION_LOCALHOST 变量并重启应用,让请求真正指向云端函数地址。

函数级部署配置:verify_jwt 与 import map

函数的行为可以在 supabase/config.toml 中按函数名逐一覆盖。最常用的是 JWT 校验开关,例如让某个 Webhook/回调类函数不要求令牌:

[functions.hello-world]
verify_jwt = false

在真实仓库的 config.toml 中可以观察到两条清晰的配置纪律:

  1. 凡是需要登录态的业务端点一律开校验browser-with-corselevenlabs-text-to-speechimage-manipulationlocationopenaipuppeteerupstash-redis-counterupstash-redis-ratelimit 等均设置为 verify_jwt = true
  2. Webhook、公开入口与 Bot 类函数关闭校验stripe-webhookstelegram-botdiscord-botslack-bot-mentionsend-email-resendcloudflare-turnstileog-image-with-storage-cdnget-tshirt-competition 等均设置为 verify_jwt = false(签名验证由函数内部自行完成,例如 Stripe Webhook 用签名密钥、Telegram/Discord 用 Bot 令牌或公钥验签)。

verify_jwt 外,config.toml 还能配置 import map 与资源文件,仓库中已有三处真实用法:

[functions.kysely-postgres]
verify_jwt = true
import_map = "./functions/import_map.json"

[functions.simple-mcp-server]
verify_jwt = false
entrypoint = "./functions/mcp/simple-mcp-server/index.ts"

[functions.wasm-modules]
verify_jwt = true
static_files = [ "./functions/wasm-modules/add-wasm/pkg/*.wasm"]
  • import_map 为函数指定依赖映射文件(本仓库统一使用 supabase/functions/import_map.json);
  • entrypoint 在函数目录内存在多个可执行文件时显式指定入口;
  • static_files 用于随函数分发静态资源(此处把 Rust 编译产出的 .wasm 文件一并部署,支撑 wasm-modules 的 WebAssembly 调用演示)。

GitHub Actions 自动部署:推送即上线

对于需要持续迭代的函数,示例仓库提供了可开箱即用的 CI/CD 方案:每当代码推送到或合并进 main 分支(也可手动触发 workflow_dispatch)时,自动部署全部 Edge Functions。工作流文件位于 .github/workflows/deploy.yaml,其核心骨架如下:

name: Deploy Function

on:
  push:
    branches:
      - main
  workflow_dispatch:

jobs:
  deploy:
    runs-on: ubuntu-latest

    env:
      SUPABASE_ACCESS_TOKEN: ${{ secrets.SUPABASE_ACCESS_TOKEN }}
      PROJECT_ID: your-project-id

    steps:
      - uses: actions/checkout@v3

      - uses: supabase/setup-cli@v1
        with:
          version: latest

      - run: supabase functions deploy --project-ref $PROJECT_ID

仓库内的 deploy.yaml 与上例略有演进:项目引用 ID 同样放入仓库 Secrets(SUPABASE_PROJECT_ID),checkout 与 setup-cli 两个 Action 均固定到具体 commit 版本以保证可复现,同时为 Job 声明了最小 permissions: contents: read 权限。整体思路一致,你需要做的准备是:

  1. 在仓库的 Actions Secrets 中配置 SUPABASE_ACCESS_TOKEN(即登录用的个人访问令牌);
  2. 配置 PROJECT_IDSUPABASE_PROJECT_ID 为你的项目引用 ID;
  3. main 分支推送作为部署触发器。

需要留意的是示例中的 supabase functions deploy --project-ref $PROJECT_ID 并未指定函数名——从 Supabase CLI v1.62.0 起支持单条命令部署项目内全部函数,这正是本工作流能一次发布所有函数的原因。

从客户端调用函数

部署完成后,即可通过官方客户端库发起调用。仓库内测试客户端使用的是 supabase-js 的 invoke 方法(参见 app/src/App.js 中的 supabase.functions.invoke(supaFunction, { body })),调用时 supabase-js 会自动附加当前登录用户的 Access Token,因此 verify_jwt = true 的函数开箱即可鉴权通过:

const { data, error } = await supabase.functions.invoke('browser-with-cors', {
  body: { name: 'world' },
})

supabase-dart 等其他语言客户端也提供了对应的 invoke 能力,更多客户端生态可在 supabase-community 组织下关注更新。至此,从本地联调到云端部署再到客户端调用的完整闭环已经打通:本地用 supabase functions serve 迭代,线上用 supabase functions deploy 发布,浏览器测试客户端 app 同时服务于本地与云端两种场景,而 config.toml 与 GitHub Actions 则分别保证了函数级配置的一致性和发布的自动化。

登录后查看全文
热门项目推荐
相关项目推荐