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&param2=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