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