首页
/ Supabase Edge Functions 实战指南:基于 Deno 边缘运行时的 TypeScript 无服务器函数

Supabase Edge Functions 实战指南:基于 Deno 边缘运行时的 TypeScript 无服务器函数

2026-09-06 16:16:36作者:裴锟轩Denise

Supabase Edge Functions 让你用 TypeScript 在全球边缘节点运行服务端代码,并与平台的数据库、Auth、Storage、Realtime 无缝集成。本文以仓库中的 edge-functions.md 文档为核心骨架,结合 examples/edge-functions 示例工程,完整覆盖函数能力特性、代码编写模式(含 withSupabase 三种鉴权模式)、config.toml 函数级配置、本地开发与部署流程,以及基于 GitHub Actions 的 CI/CD 方案。读完本文,你可以直接按仓库示例搭建、调试并部署自己的 Edge Function。

一、核心定位与技术架构

Edge Functions 是 Supabase 平台的服务端计算能力:函数以 TypeScript 编写,运行在开源 Deno 运行时(V8 引擎)之上,部署在全球边缘节点,代码执行位置靠近终端用户以获得低延迟。它们与平台其他组件的深度集成是主要卖点——函数可以直接访问数据库、Auth、Storage 与 Realtime,而不需要额外的胶水代码。

根据 edge-functions.md 中的技术细节说明,Edge Functions 的关键属性如下:

维度 说明
运行时 Deno(开源,V8 内核)
语言 TypeScript、JavaScript
NPM 支持 通过 npm: 导入说明符使用 200 万+ NPM 模块
扩缩容 自动扩缩容,无需手动调优
安全 内置 SSL、防火墙、DDoS 防护
开源程度 本地与生产环境运行同一套 Edge Runtime,无供应商锁定

"本地与生产使用同一运行时"这一点在仓库中可以直接印证:官方建议的本地开发流程就是直接用 Supabase CLI 在本地拉起同一套边缘运行时,函数代码无需为生产环境做任何改动。

二、关键特性逐条解读

edge-functions.md 列出了九项关键特性,这里逐项展开:

  1. 全球部署(Global deployment):函数在全球边缘节点运行,就近服务用户以降低延迟。
  2. TypeScript 优先:完整类型检查与编辑器自动补全,配合 Deno Language Server 可获得跳转定义、自动补全等 IDE 能力。
  3. Node.js 兼容:支持 Node.js API,并通过 npm: 说明符直接使用海量 NPM 模块。
  4. 零配置:平台预置了访问所属 Supabase 项目(数据库、Auth、Storage)所需的环境变量,函数内可直接 Deno.env.get() 取值。
  5. 数据库 Webhooks:在表发生 INSERT、UPDATE、DELETE 事件时自动触发函数,是构建数据库事件驱动逻辑的核心机制。
  6. 区域调用(Regional invocation):可选项地让函数固定运行在靠近数据库的区域,降低数据库访问延迟——适合数据库密集型负载。
  7. 内建可观测性:实时日志流、可用 SQL 查询的 Log Explorer、以及指标仪表盘。
  8. CI/CD:通过 Supabase CLI 部署,支持 GitHub Actions 自动化。
  9. 本地开发体验:热代码重载、Language Server 自动补全,且本地与生产使用相同运行时。

这些特性在示例工程 examples/edge-functions 中都有对应落地,后文会结合具体示例代码逐一验证。

三、函数代码结构:withSupabase 与三种鉴权模式

Edge Function 的入口是导出一个带 fetch 处理函数的默认对象。仓库中的新式示例统一采用 @supabase/server 提供的 withSupabase 包装器,它在请求进入业务逻辑前完成 JWT 校验,并把 Supabase 客户端注入到上下文 ctx 中。withSupabaseauth 参数有几种模式,分别对应不同的安全边界:

auth: 'user' —— 以已登录用户身份执行,RLS 生效

select-from-table-with-auth-rls/index.ts 展示了最典型的"用户态"函数:

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

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 })
    }
  }),
}

要点有三:ctx.supabase 以经过认证的用户身份运行查询,因此 Postgres 的 Row Level Security(RLS)策略自动生效;ctx.userClaims 携带已校验的用户身份声明;调用方必须携带用户 Access Token。文件尾部注释还给出了本地验证用的 curl 命令:

curl -i --request POST 'http://localhost:54321/functions/v1/select-from-table-with-auth-rls' \
  --header 'Authorization: Bearer <USER_ACCESS_TOKEN>' \
  --header 'Content-Type: application/json' \
  --data '{"name":"Functions"}'

auth: 'secret' —— 以密钥保护的服务端端点

openai-image-generation/index.ts 是一个 AI/ML 推理端点的完整实现(正对应特性列表中的 use case),它使用 auth: 'secret' 模式,并演示了函数与 Storage 的集成:

export default {
  fetch: withSupabase({ auth: 'secret' }, async (req, ctx) => {
    const { prompt, imageUrls } = await req.json()
    // ... 调用 OpenAI images.edit 生成图片 ...
    // ctx.supabaseAdmin 使用 service role,绕过 RLS,
    // 可将生成结果上传到私有 Storage bucket
    const { error: uploadError } = await ctx.supabaseAdmin.storage
      .from('generated-images')
      .upload(outputImageName, generatedImageBlob, {
        contentType: 'image/png',
        upsert: true,
      })
    // ...
  }),
}

这里 ctx.supabaseAdmin 对应 service role 权限的客户端,不受 RLS 约束,适合服务端后台任务(上传生成物、写审计日志等),但端点本身必须用密钥保护,防止匿名调用。

auth: 'none' —— 开放端点,依赖自身签名校验

处理第三方 Webhook 的函数通常关闭 JWT 校验,改由服务商的签名机制保证安全。stripe-webhooks/index.ts 是一个标准实现:

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

const stripe = new Stripe(Deno.env.get('STRIPE_API_KEY') as string)
// This is needed in order to use the Web Crypto API in Deno.
const cryptoProvider = Stripe.createSubtleCryptoProvider()

// Stripe verifies the request via its signature, so deploy with verify_jwt = false.
export default {
  fetch: withSupabase({ auth: 'none' }, async (req) => {
    const signature = req.headers.get('Stripe-Signature')
    // 必须使用 .text() 拿到原始 body,签名校验依赖原始报文而非解析后的 JSON
    const body = await req.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 Response.json({ ok: true })
  }),
}

注释中明确了两条易错点:一是此类函数部署时必须设置 verify_jwt = false(见下节配置);二是 Stripe 签名校验依赖原始请求体,因此必须用 req.text() 而不是 req.json()。同时,Stripe.createSubtleCryptoProvider() 是 Deno 环境下使用 Web Crypto API 的必要初始化。

此外,examples/edge-functions/supabase/functions 目录还包含 Discord/Telegram/Slack bot、OAuth 流程(connect-supabase 演示了完整 OAuth2 + PKCE 授权码流程)、邮件发送(Resend/SMTP)、Upstash Redis 计数与限流、Puppeteer 截图、WASM 模块、单元测试等数十个真实场景的完整示例,可作为对应特性列表中最常见 use case 的参考实现。

四、模块依赖:npm: / jsr: 说明符与 import map

Edge Functions 的依赖来源有两类,从上面示例代码中可以直接看到:

  • npm: 说明符:如 import Stripe from 'npm:stripe@^22'import { withSupabase } from 'npm:@supabase/server@^1',直接引用 NPM 模块并支持版本范围;
  • jsr: 说明符:如 openai-image-generation 中的 import OpenAI from 'jsr:@openai/openai@^6',引用 JSR 注册表。

对于 URL 导入(https://deno.land/x/...https://esm.sh/...),项目可以通过 import map 集中管理版本。examples/edge-functions/supabase/functions/import_map.json 就是一份真实配置:

{
  "imports": {
    "oak": "https://deno.land/x/oak@v11.1.0/mod.ts",
    "openai": "https://esm.sh/openai@3.1.0",
    "stripe": "https://esm.sh/stripe@11.1.0?target=deno",
    "react": "https://esm.sh/react@18.2.0",
    "kysely": "https://esm.sh/kysely@0.23.4",
    "upstash_redis": "https://deno.land/x/upstash_redis@v1.19.3/mod.ts"
  }
}

通过 import map,业务代码只需 import { Application } from 'oak',具体版本与 CDN 源由 map 文件锁定,避免每个函数文件重复写完整 URL。

五、函数级配置:config.toml 中的 [functions.<name>]

每个函数的行为在 supabase/config.toml[functions.<函数名>] 小节中配置。examples/edge-functions/supabase/config.toml 给出了最完整的参考,覆盖三种配置项:

# 1. 关闭 JWT 校验(Webhook、Bot 回调等外部触发场景)
[functions.stripe-webhooks]
verify_jwt = false

[functions.discord-bot]   # 未显式声明时默认为 verify_jwt = true

# 2. 为特定函数指定 import map
[functions.kysely-postgres]
verify_jwt = true
import_map = "./functions/import_map.json"

# 3. 自定义入口文件与打包静态资源
[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"]

参数说明:

参数 默认值 作用
verify_jwt true 是否校验请求中的 JWT。第三方 Webhook(Stripe、GitHub、Slack 等)必须置为 false,改由服务方签名机制保证安全
import_map 为该函数指定 import map 文件路径
entrypoint ./functions/<name>/index.ts 自定义函数入口文件
static_files 将本地静态文件(如 .wasm 产物)打包进部署

值得注意的是,examples/edge-functions/supabase/config.toml 中 40 余个函数只有约一半显式写了 verify_jwt——其余空小节表示沿用默认值(校验开启)。本仓库自身的 supabase/config.toml 中也为 search-embeddings 函数配置了 verify_jwt = false,说明即使是站点自身使用的内部函数也遵循同一套配置规范。

六、本地开发:与生产同一运行时

examples/edge-functions/README.md 给出的本地开发流程如下:

  1. 确保 Docker 守护进程在运行,执行 supabase start 启动完整本地栈;
  2. cp ./supabase/.env.local.example ./supabase/.env.local 生成本地环境文件,并为对应函数填入所需变量(如 OPENAI_API_KEYSTRIPE_WEBHOOK_SIGNING_SECRET);
  3. 启动函数服务:
supabase functions serve --env-file ./supabase/.env.local --no-verify-jwt

其中 --env-file 指定环境变量来源,--no-verify-jwt 在本地调试时跳过 JWT 校验(生产环境则以 config.tomlverify_jwt 为准)。函数默认在 http://localhost:54321/functions/v1/<函数名> 上提供访问。

示例仓库还附带了一个 create-react-app 测试客户端(examples/edge-functions/app 目录,可用 cd app && npm install && npm start 启动),相当于一个"类 Postman"的调用面板,既可打本地函数也可打已部署函数。本地开发期间支持热重载,编辑器配合 Deno Language Server 即可获得自动补全,且由于本地与生产使用同一 Edge Runtime,行为差异被降到最低——这正是原文档"no vendor lock-in"主张的工程含义。

七、部署与 CI/CD

手动部署流程

examples/edge-functions/README.md 的顺序:

# 1. 登录 CLI(先在 Dashboard 生成 Access Token,再执行)
supabase login

# 2. 关联远程项目
supabase link --project-ref your-project-ref

# 3. 上传密钥(推荐为生产环境单独准备 .env 文件)
supabase secrets set --env-file ./supabase/.env.local
supabase secrets list   # 验证,并查看平台默认注入的变量

# 4. 部署函数
supabase functions deploy your-function-name

自 Supabase CLI v1.62.0 起,supabase functions deploy 不带函数名即可一次性部署全部函数,适合多函数项目。

GitHub Actions 自动部署

原文档"CI/CD"特性对应的仓库落地是一个完整的 Actions 工作流(示例见 examples/edge-functions/README.mdgithub-action-deploy 函数):

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

要点:SUPABASE_ACCESS_TOKEN 存为仓库 Secret;setup-cli Action 负责安装指定版本的 Supabase CLI;推送到 main 或手动触发(workflow_dispatch)即完成部署。

八、常见使用场景与仓库示例映射

edge-functions.md 列出的六类常见场景,在示例工程中都能找到对应的可运行实现:

使用场景 仓库对应示例
Webhook 处理(Stripe、GitHub、Twilio) stripe-webhookscloudflare-turnstile
服务端 API 路由与中间件 oak-server(基于 Oak 框架的完整路由应用)、restful-tasks
定时任务 通过 pg_cron + 数据库 Webhooks 触发函数(特性列表中的标准做法)
AI/ML 推理端点 openai-image-generationhuggingface-image-captioningelevenlabs-text-to-speech
邮件与通知 send-email-resendsend-email-smtpauth-hook-react-email-resend
第三方 API 集成 discord-bottelegram-botslack-bot-mention

另外两类仓库中同样有现成参考:外部 OAuth 应用授权(connect-supabase 完整实现 PKCE 授权码流程并调用 Management API),以及性能敏感型直连 Postgres 的方案(postgres-on-the-edgedrizzlekysely-postgres)。

九、小结

Supabase Edge Functions 的核心价值可以归纳为三点:其一,Deno 边缘运行时带来低延迟的全球执行,且本地与生产同构,开发即调试、调试即生产;其二,withSupabaseuser / secret / none 三种鉴权模式与预置环境变量,让函数可以以正确的安全边界复用数据库、Auth、Storage 全部平台能力,RLS 在用户态查询中自动生效;其三,config.toml 的函数级配置(verify_jwtimport_mapentrypointstatic_files)加上 CLI 部署与 GitHub Actions 工作流,构成了一条从本地热重载到持续交付的完整链路。建议以 examples/edge-functions 为起点,先跑通一个 auth: 'user' 的表查询函数,再逐步尝试 Webhook 与 AI 推理两类典型负载。

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