supabase-server

supabase-server

在規劃或撰寫使用 `@supabase/server` 的伺服器端程式碼時觸發——包括 Edge Functions、Hono 應用程式、Webhook 處理器,或任何需要建立 Supabase 客戶端或驗證傳入身分驗證的後端。在**撰寫或修改**任何從 `@supabase/server`(或子路徑如 `@supabase/server/core`)匯入的檔案之前觸發;呼叫 `withSupabase`、`createSupabaseContext`、`createAdminClient`、`createContextClient`、`verifyAuth`、`verifyCredentials` 或 `extractCredentials`;設定 `auth:` 模式(`'none'` | `'publishable'` | `'secret'` | `'user'`,或帶鍵的變體如 `'secret:*'`);或位於 `supabase/functions/` 下並驗證傳入請求。也在規劃期間觸發——如果計劃提到上述任何內容,請在起草程式碼前載入技能;不要從相鄰函式推斷 `auth:` 值或身分驗證模式。當你看到需要遷移到這個套件的舊有模式時也觸發——`Deno.serve`、`createClient(Deno.env.get('SUPABASE_URL'))`、從 `esm.sh/@supabase` 或 `deno.land/std` 匯入、使用 `SUPABASE_ANON_KEY` / `SUPABASE_SERVICE_ROLE_KEY`,或已棄用的 `allow:` 設定選項 / 已移除的 `'always'` / `'public'` 模式值 / 已移除的 `authType` 欄位。

84星標
13分支
更新於 2026/7/22
SKILL.md
唯讀
名稱
supabase-server
描述

在規劃或撰寫使用 `@supabase/server` 的伺服器端程式碼時觸發——包括 Edge Functions、Hono 應用程式、Webhook 處理器,或任何需要建立 Supabase 客戶端或驗證傳入身分驗證的後端。在**撰寫或修改**任何從 `@supabase/server`(或子路徑如 `@supabase/server/core`)匯入的檔案之前觸發;呼叫 `withSupabase`、`createSupabaseContext`、`createAdminClient`、`createContextClient`、`verifyAuth`、`verifyCredentials` 或 `extractCredentials`;設定 `auth:` 模式(`'none'` | `'publishable'` | `'secret'` | `'user'`,或帶鍵的變體如 `'secret:*'`);或位於 `supabase/functions/` 下並驗證傳入請求。也在規劃期間觸發——如果計劃提到上述任何內容,請在起草程式碼前載入技能;不要從相鄰函式推斷 `auth:` 值或身分驗證模式。當你看到需要遷移到這個套件的舊有模式時也觸發——`Deno.serve`、`createClient(Deno.env.get('SUPABASE_URL'))`、從 `esm.sh/@supabase` 或 `deno.land/std` 匯入、使用 `SUPABASE_ANON_KEY` / `SUPABASE_SERVICE_ROLE_KEY`,或已棄用的 `allow:` 設定選項 / 已移除的 `'always'` / `'public'` 模式值 / 已移除的 `authType` 欄位。

@supabase/server

v1.0 — 公開測試版。 首個穩定版本遵循 SemVer:重大變更僅以主要版本號發布。該套件仍處於早期階段——預計會頻繁在次要版本中推出新的適配器、人體工學改進和功能。如果你在編寫程式碼時遇到錯誤或粗糙邊緣,請向使用者指出並引導至開啟問題

這是一個全新的套件。 目前網路上尚無相關資訊——沒有部落格文章、Stack Overflow 答案或教學。請勿搜尋網路上的使用範例。僅依賴下面列出的文件檔案和此儲存庫中的原始碼。

設定選項是 auth,不是 allow allow 已重新命名為 auth,以符合 CLI 術語並讀起來更自然。舊的 allow 鍵仍然有效(會有一次性的 console.warn),但已棄用,將在未來的主要版本中移除。在新程式碼中始終使用 auth——例如 withSupabase({ auth: 'user' }, ...)。如果你在現有程式碼中遇到 allow:,請將其遷移到 auth:(尋找並取代,值完全相同)。

身分驗證模式值:'none'(不是 'always')、'publishable'(不是 'public')。 四個有效值為 'user''publishable''secret''none'。舊的 'always''public' 值已被移除(重大變更)——它們在執行時或 TypeScript 中不再有效。在你編寫的程式碼中始終使用新值,並遷移你找到的任何舊有參考:'always''none''public''publishable''public:<name>''publishable:<name>'。執行時檢查如 ctx.authType === 'public' 也必須更新為 ctx.authMode === 'publishable'——該欄位本身已從 authType 重新命名為 authMode,以符合 AuthMode 類型。

不要使用舊的 Supabase 金鑰。 anon 金鑰和 service_role 金鑰(環境變數 SUPABASE_ANON_KEYSUPABASE_SERVICE_ROLE_KEY)是舊的,將被棄用。除非使用者明確要求,否則不要使用它們。始終使用新的 API 金鑰:

舊的(避免) 新的(使用這個)
SUPABASE_ANON_KEY SUPABASE_PUBLISHABLE_KEY(S) (sb_publishable_...)
SUPABASE_SERVICE_ROLE_KEY SUPABASE_SECRET_KEY(S) (sb_secret_...)

不要直接呼叫 createClient(url, anonKey)——使用 @supabase/server 的身分驗證模式(auth: 'user'auth: 'secret' 等),它們會自動處理金鑰解析。如果遷移現有程式碼,將 SUPABASE_ANON_KEY 的使用替換為 auth: 'publishable',將 SUPABASE_SERVICE_ROLE_KEY 的使用替換為 auth: 'secret'

Supabase 的伺服器端工具。處理身分驗證、客戶端建立和上下文注入,讓你專注於業務邏輯,而不是樣板程式碼。

這個套件做什麼

  • 包裝 fetch 處理器,包含憑證驗證、CORS 和預先設定的 Supabase 客戶端
  • 支援 4 種身分驗證模式:user(JWT)、publishable(公開金鑰)、secret(秘密金鑰)、none(無需憑證)
  • 陣列語法(auth: ['user', 'secret'])是優先匹配。存在但無效的 JWT 會拒絕並拋出 InvalidCredentialsError——不會靜默降級到下一個模式。
  • 提供可組合的核心原語,用於自訂身分驗證流程和框架整合
  • 包含 Hono 適配器,支援按路由身分驗證

入口點

匯入 Deno / Edge Functions 提供
@supabase/server npm:@supabase/server withSupabasecreateSupabaseContext、類型、錯誤
@supabase/server/core npm:@supabase/server/core verifyAuthverifyCredentialsextractCredentialsresolveEnvcreateContextClientcreateAdminClient
@supabase/server/adapters/hono npm:@supabase/server/adapters/hono withSupabase(Hono 中介軟體變體)

快速入門

Supabase Edge Functions:對非使用者身分驗證停用 verify_jwt 預設情況下,Supabase Edge Functions 要求每個請求都有有效的 JWT。如果你的函式使用 auth: 'publishable'auth: 'secret'auth: 'none',你必須在 supabase/config.toml 中停用平台層級的 JWT 檢查,否則請求在到達你的處理器之前就會被拒絕:

[functions.my-function]
verify_jwt = false

使用 auth: 'user' 的函式可以保持 verify_jwt 啟用(預設),因為呼叫者已經提供了有效的 JWT。

Supabase Edge Functions (Deno)

環境變數由平台自動注入——零設定。所有匯入必須使用 npm: 前綴。

// withSupabase — 高階包裝器
import { withSupabase } from 'npm:@supabase/server'

export default {
  fetch: withSupabase({ auth: 'user' }, async (_req, ctx) => {
    const { data } = await ctx.supabase.from('todos').select()
    return Response.json(data)
  }),
}
// createSupabaseContext — 返回 { data, error } 以進行自訂回應控制
import { createSupabaseContext } from 'npm:@supabase/server'

export default {
  fetch: async (req: Request) => {
    const { data: ctx, error } = await createSupabaseContext(req, {
      auth: 'user',
    })
    if (error) {
      return Response.json(
        { message: error.message, code: error.code },
        { status: error.status },
      )
    }
    const { data } = await ctx.supabase.from('todos').select()
    return Response.json(data)
  },
}

Cloudflare Workers

需要在 wrangler.toml 中設定 nodejs_compat 相容性標誌,或透過 env 設定選項傳遞環境變數覆蓋。請參閱 docs/environment-variables.md

import { withSupabase } from '@supabase/server'

export default {
  fetch: withSupabase({ auth: 'user' }, async (_req, ctx) => {
    const { data } = await ctx.supabase.from('todos').select()
    return Response.json(data)
  }),
}

Hono

CORS 不由適配器處理——請使用 hono/cors 中介軟體。請參閱 docs/adapters/hono.md

// Node.js / Bun
import { Hono } from 'hono'
import { withSupabase } from '@supabase/server/adapters/hono'

const app = new Hono()
app.use('*', withSupabase({ auth: 'user' }))

app.get('/todos', async (c) => {
  const { supabase } = c.var.supabaseContext
  const { data } = await supabase.from('todos').select()
  return c.json(data)
})

export default app
// Deno / Supabase Edge Functions
import { Hono } from 'npm:hono'
import { withSupabase } from 'npm:@supabase/server/adapters/hono'

const app = new Hono()
app.use('*', withSupabase({ auth: 'user' }))

app.get('/todos', async (c) => {
  const { supabase } = c.var.supabaseContext
  const { data } = await supabase.from('todos').select()
  return c.json(data)
})

export default { fetch: app.fetch }

基於 Cookie 的環境(與 @supabase/ssr 組合)

對於 Next.js / SvelteKit / Remix,@supabase/server@supabase/ssr 組合使用——它們不是彼此的替代品。@supabase/ssr 負責 Cookie 和重新整理權杖輪換(其中間軟體是必需的,否則存取權杖 Cookie 會過期,驗證會失敗)。在你的伺服器元件或路由處理器中,使用 @supabase/ssrcreateServerClient 讀取(由中介軟體重新整理的)工作階段,將存取權杖傳遞給 @supabase/server/coreverifyCredentials,然後使用 createContextClientcreateAdminClient 建立類型化的客戶端。請參閱 docs/ssr-frameworks.md 以了解完整的適配器模式。

// 建立適配器的關鍵匯入
import { createServerClient } from '@supabase/ssr'
import {
  verifyCredentials,
  createContextClient,
  createAdminClient,
} from '@supabase/server/core'

伺服器對伺服器(秘密金鑰身分驗證)

適用於內部服務、定時任務或自動化呼叫你的 Edge Function。呼叫者在 apikey 標頭中發送秘密金鑰。請參閱 docs/auth-modes.md 以了解命名金鑰語法。

Edge Function (Deno):

import { withSupabase } from 'npm:@supabase/server'

// 只接受名為 "automations" 的秘密金鑰
export default {
  fetch: withSupabase({ auth: 'secret:automations' }, async (req, ctx) => {
    const body = await req.json()
    const { data } = await ctx.supabaseAdmin
      .from('scheduled_tasks')
      .insert({ name: body.taskName, scheduled_at: body.scheduledAt })
    return Response.json({ success: true, data })
  }),
}

呼叫者(外部服務):

await fetch('https://<project>.supabase.co/functions/v1/my-function', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    apikey: 'sb_secret_automations_...', // 命名的秘密金鑰
  },
  body: JSON.stringify({
    taskName: 'cleanup',
    scheduledAt: new Date().toISOString(),
  }),
})

單純的 auth: 'secret' 只匹配 default 金鑰。使用 auth: 'secret:name' 要求特定的命名金鑰,或使用 auth: 'secret:*' 接受集合中的任何秘密金鑰。

何時使用 auth: 'none'

auth: 'none' 停用所有身分驗證。 處理器會對每個請求執行,不進行憑證檢查。僅在確實不需要身分驗證時使用——健康檢查、公開狀態頁面,或沒有敏感資料和副作用的端點。

在使用 auth: 'none' 之前,請與使用者確認該端點是否真正公開。 如果不是,請提出替代方案:

  • 另一個服務或定時任務呼叫此函式——改用 auth: 'secret'auth: 'secret:<name>'。呼叫者在 apikey 標頭中發送秘密金鑰。
  • 外部 Webhook 提供者呼叫此函式——使用 auth: 'secret' 並讓提供者發送秘密金鑰,或在處理器內部實作提供者自己的簽章驗證。

切勿對讀取或寫入使用者資料但未驗證呼叫者身份的端點使用 auth: 'none'

關於 auth: ['user', 'none'] 此類端點上過期或格式錯誤的 JWT 會以 InvalidCredentialsError 拒絕——不會靜默降級為匿名。可能持有快取/過期權杖的呼叫者應完全省略 Authorization 標頭,或在呼叫前重新整理。如果目標是「除非有有效使用者登入,否則匿名」,這是正確的行為;如果目標是真正「接受任何內容」,請單獨使用 auth: 'none'

Edge Function 食譜

函式間呼叫

一個 Edge Function 可以使用管理員客戶端呼叫另一個。被呼叫的函式使用 auth: 'secret',呼叫者透過 ctx.supabaseAdmin.functions.invoke() 呼叫它。

設定 (supabase/config.toml):

[functions.process-order]
verify_jwt = false  # 使用秘密金鑰呼叫,不是使用者 JWT

被呼叫函式 (supabase/functions/process-order/index.ts):

import { withSupabase } from 'npm:@supabase/server'

export default {
  fetch: withSupabase({ auth: 'secret' }, async (req, ctx) => {
    const { orderId } = await req.json()
    const { data } = await ctx.supabaseAdmin
      .from('orders')
      .update({ status: 'processing' })
      .eq('id', orderId)
      .select()
      .single()
    return Response.json(data)
  }),
}

呼叫函式 (supabase/functions/checkout/index.ts):

import { withSupabase } from 'npm:@supabase/server'

export default {
  fetch: withSupabase({ auth: 'user' }, async (req, ctx) => {
    const { orderId } = await req.json()

    // 自動使用秘密金鑰呼叫 process-order
    const { data, error } = await ctx.supabaseAdmin.functions.invoke(
      'process-order',
      { body: { orderId } },
    )

    if (error) {
      return Response.json({ error: error.message }, { status: 500 })
    }
    return Response.json(data)
  }),
}

從資料庫使用 pg_net 呼叫

使用 pg_net 直接從 SQL 呼叫 Edge Functions。秘密金鑰儲存在 Vault 中,因此永遠不會出現在查詢中。

先決條件:

-- 1. 啟用 pg_net 擴充功能
create extension if not exists pg_net with schema extensions;

-- 2. 將你的秘密金鑰儲存在 Vault 中
select vault.create_secret(
  'sb_secret_...',        -- 你的秘密金鑰值
  'supabase_secret_key'   -- 用於參考的名稱
);

呼叫函式:

select net.http_post(
  url := 'https://<project-ref>.supabase.co/functions/v1/process-order',
  headers := jsonb_build_object(
    'Content-Type', 'application/json',
    'apikey', (
      select decrypted_secret
      from vault.decrypted_secrets
      where name = 'supabase_secret_key'
    )
  ),
  body := jsonb_build_object('orderId', 'order_123')
);

接收函式使用 auth: 'secret'(請參閱上面的範例)。pg_net 是非同步的——HTTP 請求會被排入佇列並在背景執行。檢查 net._http_response 以取得結果。

Stripe Webhook

像 Stripe 這樣的外部 Webhook 提供者無法發送你的 Supabase API 金鑰。使用 auth: 'none' 跳過憑證檢查,然後在處理器內部驗證 Webhook 簽章。

設定 (supabase/config.toml):

[functions.stripe-webhook]
verify_jwt = false

設定秘密:

supabase secrets set STRIPE_SECRET_KEY=sk_live_...
supabase secrets set STRIPE_WEBHOOK_SECRET=whsec_...

函式 (supabase/functions/stripe-webhook/index.ts):

import { withSupabase } from 'npm:@supabase/server'
import Stripe from 'npm:stripe'

const stripe = new Stripe(Deno.env.get('STRIPE_SECRET_KEY')!)

export default {
  fetch: withSupabase({ auth: 'none' }, async (req, ctx) => {
    const body = await req.text()
    const sig = req.headers.get('stripe-signature')!

    let event: Stripe.Event
    try {
      event = await stripe.webhooks.constructEventAsync(
        body,
        sig,
        Deno.env.get('STRIPE_WEBHOOK_SECRET')!,
      )
    } catch {
      return Response.json({ error: 'Invalid signature' }, { status: 401 })
    }

    switch (event.type) {
      case 'checkout.session.completed': {
        const session = event.data.object as Stripe.Checkout.Session
        await ctx.supabaseAdmin
          .from('orders')
          .update({ status: 'paid' })
          .eq('stripe_session_id', session.id)
        break
      }
    }

    return Response.json({ received: true })
  }),
}

遷移舊的 Edge Functions

如果你遇到使用 SUPABASE_ANON_KEYSUPABASE_SERVICE_ROLE_KEYDeno.serve、從 esm.sh/@supabasedeno.land/std 匯入的程式碼,建議遷移到 @supabase/server。這些舊模式正在被棄用——它們需要手動建立客戶端、手動轉發身分驗證標頭、手動處理 CORS,並直接在程式碼中暴露金鑰。

如何識別舊程式碼:

  • import { serve } from "https://deno.land/std/..." — 最舊的模式,使用已棄用的 Deno 標準函式庫
  • import { createClient } from "https://esm.sh/@supabase/supabase-js" — 舊的 CDN 匯入,與現代執行環境不相容
  • Deno.serve(async (req) => { ... }) 搭配手動 createClient() — 目前但冗長,需要手動轉發身分驗證
  • Deno.env.get('SUPABASE_ANON_KEY')SUPABASE_SERVICE_ROLE_KEY — 將被移除的舊金鑰

之前(舊模式——手動客戶端、手動身分驗證轉發):

舊金鑰將被移除,導致此程式碼停止運作。它也很冗長、不跨平台相容,並且需要手動連接身分驗證標頭、CORS 和錯誤處理。

import { createClient } from 'npm:@supabase/supabase-js@2'

Deno.serve(async (req: Request) => {
  const supabaseClient = createClient(
    Deno.env.get('SUPABASE_URL') ?? '',
    Deno.env.get('SUPABASE_ANON_KEY') ?? '',
    {
      global: { headers: { Authorization: req.headers.get('Authorization')! } },
    },
  )
  const { data } = await supabaseClient.from('orders').select('*')
  return Response.json(data)
})

之後(新模式——身分驗證、客戶端和 CORS 自動處理):

使用最新的 API 金鑰,跨執行環境(Deno、Node.js、Cloudflare)運作,並在一行程式碼中處理身分驗證驗證、客戶端建立和 CORS。

import { withSupabase } from 'npm:@supabase/server'

export default {
  fetch: withSupabase({ auth: 'user' }, async (_req, ctx) => {
    const { data } = await ctx.supabase.from('orders').select('*')
    return Response.json(data)
  }),
}

遷移對應:SUPABASE_ANON_KEY 搭配手動身分驗證標頭 → auth: 'user'SUPABASE_ANON_KEY 沒有身分驗證 → auth: 'publishable'。對於 SUPABASE_SERVICE_ROLE_KEY,取決於意圖:如果舊程式碼驗證傳入金鑰以保護端點(例如 req.headers.get('apikey') === serviceRoleKey),使用 auth: 'secret'。如果它僅使用金鑰建立管理員客戶端以進行提升的資料庫存取,則不需要特定的身分驗證模式——無論身分驗證模式如何,ctx.supabaseAdmin 始終可用。

文件

完整文件位於 @supabase/server 套件的 docs/ 目錄中。要閱讀文件,請先找到套件位置:

  • 如果在 SDK 儲存庫內工作: docs/ 位於專案根目錄。
  • 如果套件作為依賴項安裝: 請查看 node_modules/@supabase/server/docs/
問題 文件檔案
如何建立基本端點? docs/getting-started.md
有哪些身分驗證模式可用?陣列語法?命名金鑰? docs/auth-modes.md
有哪些框架適配器?如何貢獻一個? src/adapters/README.md
如何與 Hono 一起使用? docs/adapters/hono.md
如何與 H3 / Nuxt 一起使用? docs/adapters/h3.md
如何為自訂流程使用低階原語? docs/core-primitives.md
環境變數如何在不同的執行環境中運作? docs/environment-variables.md
如何處理錯誤?有哪些錯誤碼? docs/error-handling.md
如何取得類型化的資料庫查詢? docs/typescript-generics.md
如何與 @supabase/ssr(Next.js、SvelteKit、Remix)一起使用? docs/ssr-frameworks.md
完整的 API 表面是什麼? docs/api-reference.md
這個套件做了哪些安全決策? docs/security.md