Supabase Edge Functions 实战:Deno 运行时与 TypeScript 开发指南
凌晨三点,我的手机疯狂震动。Stripe Webhook 在生产环境狂报 500 错误,客户付款成功了,订单却没创建。
我爬起来查日志,发现问题出在原来的 serverless 函数上——冷启动时间太长,Stripe 等不及就超时了。更头疼的是,我还要再搭一套 API 网关来验证签名、处理 CORS……
那晚之后,我开始认真研究 Supabase Edge Functions。说实话,一开始看到 “Deno 运行时” 这几个字,我也有点犹豫——毕竟 Node.js 写了这么多年,换运行时意味着重新学习一套 API。但折腾下来发现,Edge Functions 的设计思路完全不一样:它不是让你 “迁移” 过来,而是给你一个更轻量的选择,专门处理那些不需要重量级依赖的场景。
这篇文章会分享我踩过的坑和学到的东西:Edge Functions 的架构原理、Deno 和 Node.js 的差异、本地开发调试流程,以及用 Hono 框架优雅地写 API 的实战经验。
什么是 Edge Functions —— 架构与技术选型
先说清楚 Edge Functions 是什么,以及为什么 Supabase 选择 Deno 而不是 Node.js。
边缘执行,不是云端托管
Edge Functions 是跑在边缘节点上的 TypeScript 函数。和传统的 Lambda、Vercel Functions 不一样,它不是集中部署在某几个大区的服务器上,而是分散在全球几百个边缘节点里。
这意味着什么?你的用户在上海发起请求,函数可能就在东京的边缘节点执行,延迟从几百毫秒降到几十毫秒。
但边缘也有代价——函数不能太重。每个函数独立运行在 V8 isolate 里,有自己的内存堆和执行线程,启动速度在毫秒级,但内存有限、执行时间也有限制。所以它适合短生命周期操作:Webhook 处理、OG 图片生成、第三方 API 调用、邮件发送这些。
不适合的也有:长时间运行的任务、依赖大量 Node.js 原生模块的库、需要访问文件系统的操作。
为什么是 Deno
这个问题我在 Supabase 的 GitHub Discussion 里翻了很久,官方的解释大概是:
- 启动快:Deno 用 ESZip 格式打包代码,函数冷启动能做到 0-5ms,而 Node.js 的 Lambda 冷启动通常在 100-500ms。
- 安全模型:Deno 默认禁用文件系统访问、网络访问,需要显式授权。这在多租户的边缘环境里很重要——你不想别人的函数读到你的数据吧?
- TypeScript 原生支持:不用配 tsconfig、不用装 ts-node,写
.ts文件直接跑。对于早就用 TypeScript 写后端的人来说,这省了不少配置时间。 - 可移植:Deno 可以嵌入到其他应用里。Supabase 用的是他们自己维护的 Deno 分支,叫
deno_core,专门为嵌入式场景做了改进。
有得也有失。Deno 的生态比 Node.js 小不少,有些 npm 包没法直接用。不过 Deno 现在支持 npm specifiers,可以 import { xxx } from 'npm:lodash',兼容性好多了。
架构一瞥
请求进来之后大概是这样的流程:
客户端 → CDN/边缘网关 → JWT 验证 → V8 isolate 执行函数 → 响应返回
重点在那个 JWT 验证——Edge Functions 默认会验证请求里的 Authorization header,确保只有授权用户能调用。如果你想公开访问,需要在部署时加 --no-verify-jwt 标志。
开发环境搭建与 CLI 命令详解
好了,概念讲完了。开始动手。
安装 Supabase CLI
我用的是 macOS,直接 Homebrew 安装:
brew install supabase/tap/supabase
Linux 和 Windows 也有对应的安装方式,官方文档写得很清楚,我就不重复了。
安装完之后登录:
supabase login
这步会打开浏览器让你授权 CLI 访问你的 Supabase 账户。
初始化项目
在你的项目目录下执行:
supabase init
这会创建一个 supabase/ 目录,里面有配置文件 config.toml 和一个 functions/ 子目录(如果不存在会自动创建)。
创建第一个 Edge Function
supabase functions new hello-world
这个命令会在 supabase/functions/ 下创建一个 hello-world/ 目录,里面有一个 index.ts 文件,长这样:
Deno.serve(async (req: Request) => {
const { name } = await req.json()
const data = {
message: `Hello ${name}!`,
}
return new Response(JSON.stringify(data), {
headers: {
'Content-Type': 'application/json',
'Connection': 'keep-alive',
},
})
})
嗯,就这么简单。Deno.serve() 是 Deno 的原生 API,接收一个请求处理函数。Request 和 Response 都是标准的 Web API,和浏览器里的 fetch 用法一样。
本地开发服务器
启动本地开发环境:
supabase functions serve --env-file supabase/.env.local
这会启动一个本地服务器,默认地址是 http://localhost:54321。你的函数可以通过 http://localhost:54321/functions/v1/hello-world 访问。
说实话我第一次跑的时候踩了个坑——忘了先启动 Supabase 的本地服务栈(包括本地 PostgreSQL)。正确的方式是:
# 先启动本地 Supabase 栈
supabase start
# 再启动函数服务
supabase functions serve
测试请求
用 curl 或者 HTTPie 发个请求试试:
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":"World"}'
返回:
{
"message": "Hello World!"
}
成功了。
热重载是自动开启的,改代码保存之后立即生效,不用重启服务。这点体验挺好。
环境变量
敏感信息别写在代码里。Supabase 支持通过 .env 文件管理环境变量:
# 创建 .env 文件
echo "MY_SECRET=super_secret_value" > supabase/.env.local
# 在函数里读取
const mySecret = Deno.env.get('MY_SECRET')
部署到生产环境的时候,用 supabase secrets set 命令:
supabase secrets set MY_SECRET=super_secret_value
实战:用 Hono 框架构建 RESTful API
原生的 Deno.serve() 够用,但当你的函数逻辑变复杂——需要路由、中间件、参数验证——手写会很痛苦。
这时候 Hono 就派上用场了。
什么是 Hono
Hono 是一个超轻量的 Web 框架,专门为边缘运行时设计。它支持 Deno、Cloudflare Workers、Bun 等多种运行时,路由性能很强,而且 TypeScript 支持一流。
官方说它 “small, simple, and ultrafast” ——我用下来感觉确实如此。
集成到 Edge Functions
先创建一个新的函数:
supabase functions new user-api
然后修改 index.ts:
import { Hono } from 'jsr:@hono/hono'
import { cors } from 'jsr:@hono/hono/cors'
import { logger } from 'jsr:@hono/hono/logger'
const app = new Hono().basePath('/api')
// 中间件
app.use('*', cors())
app.use('*', logger())
// 路由定义
app.get('/users/:id', (c) => {
const id = c.req.param('id')
return c.json({ user: { id, name: 'Demo User', email: 'demo@example.com' } })
})
app.post('/users', async (c) => {
const body = await c.req.json<{ name: string; email: string }>()
// 这里可以接入 Supabase 数据库
return c.json({ created: body }, 201)
})
app.put('/users/:id', async (c) => {
const id = c.req.param('id')
const body = await c.req.json<{ name?: string; email?: string }>()
return c.json({ updated: { id, ...body } })
})
app.delete('/users/:id', (c) => {
const id = c.req.param('id')
return c.json({ deleted: id })
})
// 启动服务
Deno.serve(app.fetch)
几个要点:
jsr:@hono/hono是 Deno 的 JSR 包管理格式,不是 npm。JSR 是 Deno 官方的包仓库。basePath('/api')让你的路由前缀变成/api。c是 Hono 的 context 对象,包含请求、响应和各种工具方法。c.json()自动设置 Content-Type 头,还能处理 null 和 undefined。
连接 Supabase 数据库
Hono 只是 Web 框架,要操作数据库还得用 Supabase 客户端。这里有个完整例子:
import { Hono } from 'jsr:@hono/hono'
import { createClient } from 'jsr:@supabase/supabase-js@2'
const app = new Hono().basePath('/api')
// 初始化 Supabase 客户端
const supabaseUrl = Deno.env.get('SUPABASE_URL')!
const supabaseKey = Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')!
const supabase = createClient(supabaseUrl, supabaseKey, {
auth: {
autoRefreshToken: false,
persistSession: false,
},
})
// GET /api/users - 列表
app.get('/users', async (c) => {
const { data, error } = await supabase
.from('users')
.select('id, name, email, created_at')
if (error) {
return c.json({ error: error.message }, 500)
}
return c.json({ users: data })
})
// POST /api/users - 创建
app.post('/users', async (c) => {
const body = await c.req.json<{ name: string; email: string }>()
const { data, error } = await supabase
.from('users')
.insert(body)
.select()
.single()
if (error) {
return c.json({ error: error.message }, 400)
}
return c.json({ user: data }, 201)
})
Deno.serve(app.fetch)
注意我用的是 SUPABASE_SERVICE_ROLE_KEY,这个 key 有完整的数据库权限,绕过 RLS。在生产环境要小心使用。
错误处理和验证
Hono 没有内置验证器,但可以配合 Zod 使用:
import { z } from 'npm:zod'
import { zValidator } from 'jsr:@hono/zod-validator'
const userSchema = z.object({
name: z.string().min(1).max(100),
email: z.string().email(),
})
app.post(
'/users',
zValidator('json', userSchema),
async (c) => {
const validated = c.req.valid('json')
// validated 已经是类型安全的对象了
return c.json({ received: validated })
}
)
验证失败会自动返回 400 错误,响应体里有详细的错误信息。
部署与生产环境最佳实践
本地跑通了,该上生产了。
部署命令
supabase functions deploy user-api
第一次部署会问你要链接哪个 Supabase 项目。之后会自动上传代码、构建、部署。
部署成功后,函数的 URL 格式是:
https://[PROJECT_ID].supabase.co/functions/v1/user-api
环境变量和 Secrets
生产环境的环境变量需要单独设置:
supabase secrets set SUPABASE_URL=https://xxx.supabase.co
supabase secrets set SUPABASE_SERVICE_ROLE_KEY=eyJxxx...
这些 Secrets 会被加密存储,函数运行时通过 Deno.env.get() 读取。
JWT 验证策略
前面说过,Edge Functions 默认会验证 JWT。这意味着:
- 只有带有效
Authorization: Bearer <token>的请求才能通过 - Token 里包含的用户信息可以从
req.headers里解析
如果你要公开 API(比如给第三方 Webhook 用),部署时加上 --no-verify-jwt:
supabase functions deploy user-api --no-verify-jwt
但这意味着任何人都能调用你的函数,需要自己在代码里做验证。
减少冷启动延迟
虽然 Deno 冷启动很快,但还是有一些事情可以做:
- 减少依赖体积:尽量用 Deno/JSR 原生包,少用 npm 包
- 延迟加载:大的模块按需
import() - 保持函数轻量:一个函数只做一件事,别把整个后端塞进去
Supabase 官方建议单函数执行时间不超过 2 秒,冷启动时间在 0-5ms。所以下面这些建议能帮你把响应做得更快:
监控和日志
Dashboard 里可以查看函数的调用日志和错误报告。也可以集成 Sentry 或其他监控服务。
另外有个 EdgeRuntime.waitUntil() API,可以让函数在返回响应后继续执行后台任务:
EdgeRuntime.waitUntil(
fetch('https://analytics.example.com/track', { method: 'POST', body: '...' })
)
return new Response('OK')
这样客户端不用等后台任务完成就能收到响应。
结论
说了这么多,Edge Functions 到底适合什么场景?
适合的:
- Webhook 处理(Stripe、GitHub、Slack)
- OG 图片生成
- AI 推理(调用 LLM API)
- 邮件和消息通知
- 短生命周期的数据处理
不太适合的:
- 长时间运行的任务(比如视频转码)
- 依赖重量级 Node.js 原生模块的库
- 需要访问文件系统的操作
如果你已经在用 Supabase 的数据库和认证,Edge Functions 是个很自然的扩展——不用搭额外的服务器,不用操心运维,直接写业务逻辑就行。
和 Cloudflare Workers 或 Vercel Functions 比呢?我觉得各有优势。Cloudflare Workers 更成熟、生态更大,Vercel Functions 和 Next.js 生态绑定更深。但如果你已经用 Supabase,Edge Functions 的集成体验是最好的——数据库客户端、认证、存储都是现成的。
想试试的话,可以从官方示例库入手:github.com/supabase/supabase/tree/master/examples/edge-functions
有什么问题欢迎在评论区留言,或者直接去 Supabase Discord 找社区帮忙。
Supabase Edge Functions 开发部署完整流程
从环境搭建到生产部署的完整操作指南
⏱️ 预计耗时: 45 分钟
- 1
步骤1: 安装 Supabase CLI 并登录
使用 Homebrew 安装 CLI(macOS):
```bash
brew install supabase/tap/supabase
supabase login
```
登录后会打开浏览器授权 CLI 访问你的 Supabase 账户。 - 2
步骤2: 初始化项目并创建函数
在项目目录下执行初始化命令,然后创建你的第一个函数:
```bash
supabase init
supabase functions new hello-world
```
这会在 `supabase/functions/` 目录下创建函数模板。 - 3
步骤3: 启动本地开发环境
先启动本地 Supabase 栈(包含 PostgreSQL),再启动函数服务:
```bash
supabase start
supabase functions serve --env-file supabase/.env.local
```
本地函数地址:`http://localhost:54321/functions/v1/{function-name}` - 4
步骤4: 使用 Hono 框架构建 API
安装 Hono 并创建 RESTful API:
```typescript
import { Hono } from 'jsr:@hono/hono'
import { cors } from 'jsr:@hono/hono/cors'
const app = new Hono().basePath('/api')
app.use('*', cors())
app.get('/users/:id', (c) => {
return c.json({ user: { id: c.req.param('id') } })
})
Deno.serve(app.fetch)
```
Hono 支持路由、中间件和参数验证。 - 5
步骤5: 配置环境变量和 Secrets
本地开发使用 `.env` 文件,生产环境使用 Secrets:
```bash
# 本地
echo "MY_SECRET=value" > supabase/.env.local
# 生产
supabase secrets set MY_SECRET=value
```
函数内通过 `Deno.env.get('MY_SECRET')` 读取。 - 6
步骤6: 部署到生产环境
部署函数并设置公开访问(如需要):
```bash
# 标准部署(需要 JWT 验证)
supabase functions deploy user-api
# 公开 API(无 JWT 验证)
supabase functions deploy user-api --no-verify-jwt
```
生产 URL 格式:`https://[PROJECT_ID].supabase.co/functions/v1/user-api`
常见问题
Supabase Edge Functions 和 Cloudflare Workers 有什么区别?
Edge Functions 适合处理 Webhook 吗?
Deno 和 Node.js 的包管理有什么不同?
如何在 Edge Functions 中连接 Supabase 数据库?
Edge Functions 有执行时间限制吗?
如何调试 Edge Functions?
11 分钟阅读 · 发布于: 2026年4月19日 · 修改于: 2026年4月19日
相关文章
Supabase 入门:PostgreSQL + Auth + Storage 一站式后端
Supabase 入门:PostgreSQL + Auth + Storage 一站式后端
Supabase 数据库设计:表结构、关系与 Row Level Security 完全指南
Supabase 数据库设计:表结构、关系与 Row Level Security 完全指南
Supabase Auth 实战:邮箱验证、OAuth 与会话管理

评论
使用 GitHub 账号登录后即可评论