Supabase Edge Functions创建与部署 #
一、创建函数 #
1.1 使用CLI创建 #
bash
# 创建新函数
supabase functions new hello-world
# 创建后的目录结构
supabase/
└── functions/
└── hello-world/
└── index.ts
1.2 默认模板 #
typescript
// 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',
},
})
})
二、本地开发 #
2.1 启动本地服务 #
bash
# 启动所有本地服务(包括函数)
supabase start
# 只启动函数服务
supabase functions serve
2.2 本地测试 #
bash
# 使用curl测试
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"}'
2.3 热重载 #
bash
# 函数服务会自动检测文件变化并重新加载
# 修改 index.ts 后自动生效
三、部署函数 #
3.1 部署单个函数 #
bash
# 部署到远程
supabase functions deploy hello-world
# 指定项目
supabase functions deploy hello-world --project-ref your-project-ref
3.2 部署所有函数 #
bash
# 部署所有函数
supabase functions deploy
3.3 部署确认 #
bash
# 输出
Deploying Function (hello-world)
Function deployed successfully
URL: https://your-project.supabase.co/functions/v1/hello-world
四、环境变量 #
4.1 本地环境变量 #
bash
# supabase/.env
MY_SECRET=secret-value
API_KEY=your-api-key
4.2 设置远程密钥 #
bash
# 设置单个密钥
supabase secrets set MY_SECRET=secret-value
# 从文件设置
supabase secrets set --env-file .env
# 查看密钥列表
supabase secrets list
# 删除密钥
supabase secrets delete MY_SECRET
4.3 使用环境变量 #
typescript
Deno.serve(async (req) => {
const mySecret = Deno.env.get('MY_SECRET')
return new Response(JSON.stringify({ secret: mySecret }), {
headers: { 'Content-Type': 'application/json' },
})
})
五、函数示例 #
5.1 API端点 #
typescript
// functions/api/index.ts
import { createClient } from 'https://esm.sh/@supabase/supabase-js@2'
Deno.serve(async (req) => {
const supabase = createClient(
Deno.env.get('SUPABASE_URL')!,
Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')!
)
const method = req.method
const url = new URL(req.url)
const path = url.pathname.split('/').filter(Boolean)
// GET /api/users
if (method === 'GET' && path[1] === 'users') {
const { data, error } = await supabase
.from('users')
.select('*')
return new Response(JSON.stringify({ data, error }), {
headers: { 'Content-Type': 'application/json' },
})
}
// POST /api/users
if (method === 'POST' && path[1] === 'users') {
const body = await req.json()
const { data, error } = await supabase
.from('users')
.insert(body)
.select()
return new Response(JSON.stringify({ data, error }), {
headers: { 'Content-Type': 'application/json' },
})
}
return new Response('Not Found', { status: 404 })
})
5.2 Webhook处理 #
typescript
// functions/webhook/index.ts
Deno.serve(async (req) => {
const signature = req.headers.get('x-webhook-signature')
if (!signature) {
return new Response('Missing signature', { status: 401 })
}
const body = await req.json()
// 验证签名
// 处理webhook数据
console.log('Webhook received:', body)
return new Response(JSON.stringify({ received: true }), {
headers: { 'Content-Type': 'application/json' },
})
})
5.3 发送邮件 #
typescript
// functions/send-email/index.ts
Deno.serve(async (req) => {
const { to, subject, body } = await req.json()
// 使用第三方邮件服务
const response = await fetch('https://api.resend.com/emails', {
method: 'POST',
headers: {
'Authorization': `Bearer ${Deno.env.get('RESEND_API_KEY')}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
from: 'noreply@example.com',
to,
subject,
html: body,
}),
})
const data = await response.json()
return new Response(JSON.stringify(data), {
headers: { 'Content-Type': 'application/json' },
})
})
5.4 定时任务 #
typescript
// functions/cron/index.ts
import { createClient } from 'https://esm.sh/@supabase/supabase-js@2'
Deno.serve(async (req) => {
// 验证定时任务密钥
const authHeader = req.headers.get('Authorization')
if (authHeader !== `Bearer ${Deno.env.get('CRON_SECRET')}`) {
return new Response('Unauthorized', { status: 401 })
}
const supabase = createClient(
Deno.env.get('SUPABASE_URL')!,
Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')!
)
// 执行清理任务
const { error } = await supabase
.from('sessions')
.delete()
.lt('expires_at', new Date().toISOString())
if (error) {
console.error('Cleanup failed:', error)
return new Response('Cleanup failed', { status: 500 })
}
return new Response('Cleanup completed')
})
六、配置文件 #
6.1 config.toml #
toml
# supabase/config.toml
[functions.hello-world]
verify_jwt = false # 是否验证JWT
[functions.api]
verify_jwt = true
七、调试技巧 #
7.1 本地调试 #
typescript
Deno.serve(async (req) => {
console.log('=== Debug Info ===')
console.log('Method:', req.method)
console.log('URL:', req.url)
console.log('Headers:', Object.fromEntries(req.headers))
const body = await req.text()
console.log('Body:', body)
// 处理逻辑...
return new Response('OK')
})
7.2 查看日志 #
bash
# 实时查看日志
supabase functions logs hello-world --follow
# 查看最近日志
supabase functions logs hello-world --limit 100
八、最佳实践 #
8.1 错误处理 #
typescript
Deno.serve(async (req) => {
try {
const body = await req.json()
// 验证输入
if (!body.required_field) {
return new Response(
JSON.stringify({ error: 'required_field is required' }),
{ status: 400, headers: { 'Content-Type': 'application/json' } }
)
}
// 处理逻辑
const result = await processRequest(body)
return new Response(
JSON.stringify({ success: true, data: result }),
{ headers: { 'Content-Type': 'application/json' } }
)
} catch (error) {
console.error('Error:', error)
return new Response(
JSON.stringify({ error: 'Internal server error' }),
{ status: 500, headers: { 'Content-Type': 'application/json' } }
)
}
})
8.2 响应格式 #
typescript
// 成功响应
function successResponse(data: any) {
return new Response(
JSON.stringify({ success: true, data }),
{ headers: { 'Content-Type': 'application/json' } }
)
}
// 错误响应
function errorResponse(message: string, status = 400) {
return new Response(
JSON.stringify({ success: false, error: message }),
{ status, headers: { 'Content-Type': 'application/json' } }
)
}
九、总结 #
创建与部署要点:
| 操作 | 命令 |
|---|---|
| 创建 | supabase functions new name |
| 本地运行 | supabase functions serve |
| 部署 | supabase functions deploy name |
| 设置密钥 | supabase secrets set KEY=value |
| 查看日志 | supabase functions logs name |
下一步,让我们学习本地开发!
最后更新:2026-03-28