Supabase Edge Functions调用 #
一、基础调用 #
1.1 使用客户端SDK #
typescript
import { supabase } from './lib/supabase'
// 调用函数
const { data, error } = await supabase.functions.invoke('hello-world', {
body: { name: 'World' },
})
console.log(data) // { message: 'Hello World!' }
1.2 使用fetch #
typescript
const response = await fetch(
'https://your-project.supabase.co/functions/v1/hello-world',
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${anonKey}`,
},
body: JSON.stringify({ name: 'World' }),
}
)
const data = await response.json()
1.3 使用curl #
bash
curl -X POST 'https://your-project.supabase.co/functions/v1/hello-world' \
-H 'Authorization: Bearer YOUR_ANON_KEY' \
-H 'Content-Type: application/json' \
-d '{"name":"World"}'
二、认证 #
2.1 自动传递用户令牌 #
typescript
// 使用supabase客户端会自动传递用户令牌
const { data, error } = await supabase.functions.invoke('my-function', {
body: { data: 'test' },
})
// 函数中获取用户
Deno.serve(async (req) => {
const supabase = createClient(
Deno.env.get('SUPABASE_URL')!,
Deno.env.get('SUPABASE_ANON_KEY')!,
{
global: {
headers: { Authorization: req.headers.get('Authorization')! },
},
}
)
const { data: { user } } = await supabase.auth.getUser()
if (!user) {
return new Response('Unauthorized', { status: 401 })
}
// 处理已认证用户的请求...
})
2.2 手动传递令牌 #
typescript
const { data: { session } } = await supabase.auth.getSession()
const response = await fetch(
'https://your-project.supabase.co/functions/v1/my-function',
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${session?.access_token}`,
},
body: JSON.stringify({ data: 'test' }),
}
)
2.3 服务端调用 #
typescript
import { createClient } from '@supabase/supabase-js'
const supabaseAdmin = createClient(url, serviceRoleKey, {
auth: { autoRefreshToken: false, persistSession: false },
})
// 使用service_role调用
const { data, error } = await supabaseAdmin.functions.invoke('admin-function', {
body: { action: 'delete-user', userId: '123' },
})
三、参数传递 #
3.1 请求体 #
typescript
// 发送JSON数据
const { data, error } = await supabase.functions.invoke('my-function', {
body: {
name: 'John',
email: 'john@example.com',
preferences: {
theme: 'dark',
notifications: true,
},
},
})
3.2 查询参数 #
typescript
// 通过URL传递参数
const { data, error } = await supabase.functions.invoke('my-function', {
body: {},
headers: {
'x-query-param': 'value',
},
})
// 或使用fetch
const response = await fetch(
'https://your-project.supabase.co/functions/v1/my-function?param1=value1¶m2=value2',
{
method: 'GET',
headers: {
'Authorization': `Bearer ${token}`,
},
}
)
typescript
// 函数中获取查询参数
Deno.serve(async (req) => {
const url = new URL(req.url)
const param1 = url.searchParams.get('param1')
const param2 = url.searchParams.get('param2')
return new Response(JSON.stringify({ param1, param2 }), {
headers: { 'Content-Type': 'application/json' },
})
})
3.3 路径参数 #
typescript
// RESTful风格
const response = await fetch(
'https://your-project.supabase.co/functions/v1/api/users/123',
{
method: 'GET',
headers: {
'Authorization': `Bearer ${token}`,
},
}
)
typescript
// 函数中处理路径参数
Deno.serve(async (req) => {
const url = new URL(req.url)
const pathParts = url.pathname.split('/').filter(Boolean)
// /functions/v1/api/users/123
// pathParts = ['functions', 'v1', 'api', 'users', '123']
const userId = pathParts[4]
return new Response(JSON.stringify({ userId }), {
headers: { 'Content-Type': 'application/json' },
})
})
3.4 自定义头 #
typescript
const { data, error } = await supabase.functions.invoke('my-function', {
body: { data: 'test' },
headers: {
'X-Custom-Header': 'custom-value',
},
})
四、响应处理 #
4.1 成功响应 #
typescript
const { data, error } = await supabase.functions.invoke('my-function', {
body: { name: 'John' },
})
if (error) {
console.error('Error:', error)
} else {
console.log('Data:', data)
}
4.2 错误响应 #
typescript
const { data, error } = await supabase.functions.invoke('my-function', {
body: {},
})
if (error) {
// error.message: 错误消息
// error.context: 上下文信息
console.error('Function error:', error.message)
}
4.3 处理不同状态码 #
typescript
const response = await fetch(
'https://your-project.supabase.co/functions/v1/my-function',
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`,
},
body: JSON.stringify({ data: 'test' }),
}
)
switch (response.status) {
case 200:
const data = await response.json()
console.log('Success:', data)
break
case 400:
console.error('Bad request')
break
case 401:
console.error('Unauthorized')
break
case 500:
console.error('Server error')
break
default:
console.error('Unknown error')
}
五、文件上传 #
5.1 上传文件到函数 #
typescript
const file = document.querySelector('input[type="file"]').files[0]
const formData = new FormData()
formData.append('file', file)
const response = await fetch(
'https://your-project.supabase.co/functions/v1/upload',
{
method: 'POST',
headers: {
'Authorization': `Bearer ${token}`,
},
body: formData,
}
)
5.2 函数处理文件 #
typescript
Deno.serve(async (req) => {
const formData = await req.formData()
const file = formData.get('file') as File
if (!file) {
return new Response('No file uploaded', { status: 400 })
}
const arrayBuffer = await file.arrayBuffer()
const uint8Array = new Uint8Array(arrayBuffer)
// 处理文件...
return new Response(JSON.stringify({
name: file.name,
size: file.size,
type: file.type,
}), {
headers: { 'Content-Type': 'application/json' },
})
})
六、流式响应 #
6.1 返回流 #
typescript
Deno.serve(async (req) => {
const encoder = new TextEncoder()
const stream = new ReadableStream({
async start(controller) {
for (let i = 0; i < 10; i++) {
const message = `Message ${i}\n`
controller.enqueue(encoder.encode(message))
await new Promise(resolve => setTimeout(resolve, 1000))
}
controller.close()
},
})
return new Response(stream, {
headers: {
'Content-Type': 'text/plain; charset=utf-8',
'Transfer-Encoding': 'chunked',
},
})
})
6.2 消费流 #
typescript
const response = await fetch(
'https://your-project.supabase.co/functions/v1/stream',
{ method: 'GET' }
)
const reader = response.body?.getReader()
const decoder = new TextDecoder()
while (reader) {
const { done, value } = await reader.read()
if (done) break
console.log(decoder.decode(value))
}
七、超时处理 #
7.1 客户端超时 #
typescript
const controller = new AbortController()
const timeoutId = setTimeout(() => controller.abort(), 5000)
try {
const response = await fetch(
'https://your-project.supabase.co/functions/v1/my-function',
{
method: 'POST',
signal: controller.signal,
body: JSON.stringify({ data: 'test' }),
}
)
clearTimeout(timeoutId)
const data = await response.json()
} catch (error) {
if (error.name === 'AbortError') {
console.error('Request timed out')
}
}
八、错误处理最佳实践 #
8.1 统一错误格式 #
typescript
// 函数端
Deno.serve(async (req) => {
try {
const body = await req.json()
// 验证
if (!body.required) {
return new Response(
JSON.stringify({
success: false,
error: {
code: 'VALIDATION_ERROR',
message: 'required field is missing',
},
}),
{ 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) {
return new Response(
JSON.stringify({
success: false,
error: {
code: 'INTERNAL_ERROR',
message: error.message,
},
}),
{ status: 500, headers: { 'Content-Type': 'application/json' } }
)
}
})
8.2 客户端错误处理 #
typescript
async function callFunction(name: string, body: any) {
const { data, error } = await supabase.functions.invoke(name, { body })
if (error) {
// 处理不同类型的错误
if (error.message.includes('401')) {
throw new Error('请先登录')
}
if (error.message.includes('400')) {
throw new Error('请求参数错误')
}
throw new Error('服务暂时不可用')
}
if (!data.success) {
throw new Error(data.error.message)
}
return data.data
}
九、总结 #
函数调用要点:
| 操作 | 方法 |
|---|---|
| 调用 | supabase.functions.invoke(name, options) |
| 认证 | 自动传递或手动设置Authorization |
| 参数 | body, headers |
| 错误 | 检查error对象 |
下一步,让我们学习高级特性!
最后更新:2026-03-28