Supabase 架構與元件深入解析 | Supabase 完整教學

2026/09/21
Supabase 架構與元件深入解析 | Supabase 完整教學

Supabase 的後端不是一個大黑箱,而是一群圍繞 PostgreSQL 運作的獨立開源服務。本篇拆開架構圖,帶你看清楚請求如何從 API Gateway 一路流經 PostgRESTGoTrueRealtimeStorage 到達資料庫,再看懂為什麼「所有服務共用一個 Postgres」是這套設計的靈魂。

前言

一句話定義本篇主題:Supabase 的架構是一套「以 Postgres 為圓心、由多個開源微服務環繞」的系統,而 API Gateway 是它們對外的唯一大門。

上一篇《平台總覽》我們建立了「六大服務圍繞資料庫」的心智模型。這一篇要更進一步——把每一個方框打開,看裡面到底裝了什麼、彼此怎麼協作、一個請求從你的 App 送出到拿回資料,中間經過了哪些關卡。

如果要用現實世界的類比,你可以把 Supabase 想成一座大型辦公大樓。大樓只有一個「大廳警衛台」(API Gateway)——所有訪客都得先在這裡刷卡(驗證 API 金鑰)、說明要找哪個部門(路由)。通過之後,警衛會把你帶到對應的樓層:接待部門(Auth)、檔案室(Storage)、廣播室(Realtime)、臨時工作間(Edge Functions)、或是資料查詢櫃台(REST API)。而所有部門的檔案,最終都存放在地下室那間唯一的中央檔案庫(Postgres)。部門本身不各自藏一份資料,它們只是不同的「取用視窗」,背後指向的是同一個檔案庫。

這個「唯一的中央檔案庫」是理解 Supabase 架構的鑰匙。很多人第一次接觸會誤以為 Auth 有自己的資料庫、Storage 有自己的資料庫——其實沒有,它們全都寫進同一個 Postgres,只是各自佔用不同的 schema。

本篇你會學到:

  • 請求的完整旅程:從 App 到 Gateway,再到各服務,最後抵達 Postgres 的每一步
  • 每個元件的真面目:PostgREST、GoTrue、Realtime、Storage、Edge Functions 各自的職責與底層技術
  • 受管服務背後的開源專案:每個雲端服務對應哪個 GitHub 倉庫,以及自架時它們如何組裝
  • 「單一真相來源」如何落地:各服務怎麼靠不同 schema 共用同一個資料庫

核心概念

一張圖看懂請求的旅程

先把整體架構攤開。與其記憶零散的服務名稱,不如記住這張「請求流向圖」——資料是怎麼從你的應用程式一路流到 Postgres,再流回來的。

┌─────────────────────────────────────────────────────────┐
│                     你的應用程式                          │
│              (Web / Mobile / Server / Edge)              │
└───────────────────────────┬─────────────────────────────┘
                            │ HTTPS / WSS
                            │ apikey + Authorization: Bearer <JWT>
┌───────────────────────────▼─────────────────────────────┐
│              API Gateway(Kong / Envoy)                 │
│   金鑰驗證 · 路由 · Rate Limiting · CORS · TLS 終止       │
└──┬──────────┬──────────┬───────────┬───────────┬─────────┘
   │/rest/v1  │/auth/v1  │/storage/v1│/realtime/v1│/functions/v1
   ▼          ▼          ▼           ▼            ▼
┌────────┐┌────────┐┌──────────┐┌──────────┐┌────────────┐
│PostgREST││ GoTrue ││Storage API││ Realtime ││Edge Function│
│(Haskell)││  (Go)  ││ (Node.js) ││ (Elixir) ││   (Deno)   │
└───┬────┘└───┬────┘└─────┬─────┘└────┬─────┘└──────┬─────┘
    │         │           │           │             │
    │  auth   │  storage  │  讀 WAL    │  service_   │
    │  schema │  schema   │           │  role 連線   │
    └─────────┴───────────┴───────────┴─────────────┘
                          │
              ┌───────────▼───────────┐
              │      PostgreSQL       │  ← 單一真相來源
              │  public / auth /      │    (含 RLS、Extensions)
              │  storage schema、RLS  │
              └───────────┬───────────┘
                          │
                Supavisor(連線池,取代 PgBouncer)

看這張圖時,請抓住三個重點:

  1. 只有一個入口:不論你要打哪個服務,第一站永遠是 API Gateway。它是安全與路由的統一關卡。
  2. 服務是平行的、獨立的:PostgREST、GoTrue、Realtime、Storage、Edge Functions 彼此不互相呼叫,它們是各自獨立的行程(甚至用不同語言寫成)。
  3. 終點都是 Postgres:無論走哪條路,資料最終都落在同一個資料庫。差別只在它們寫進不同的 schema、用不同的方式讀取(HTTP 查詢 vs 讀取 WAL 日誌)。

第一關:API Gateway(Kong / Envoy)

所有外部請求的統一入口就是 API Gateway。在 Supabase 的演進中,早期版本使用 Kong,較新版本改用 Envoy,兩者職責相同。它負責的工作包括:

  • API 金鑰驗證:檢查請求 Header 裡的 apikey,區分這是公開的 anon(匿名)金鑰,還是可繞過 RLS 的 service_role(服務角色)金鑰。
  • 路由:依照網址路徑,把請求分派到對應的後端服務(見下方路由表)。
  • Rate Limiting(速率限制):防止濫用與 DDoS。
  • CORS 處理:管理跨來源資源共享政策。
  • TLS 終止:處理 HTTPS / WSS 的 SSL 解密。

Gateway 的路由規則非常直觀,就是「看路徑分流」:

/rest/v1/*       → PostgREST(資料庫自動 REST API)
/auth/v1/*       → GoTrue(身份驗證)
/storage/v1/*    → Storage API(物件儲存)
/realtime/v1/*   → Realtime(WebSocket 即時訂閱)
/functions/v1/*  → Edge Functions(Deno 執行環境)

這裡有個關鍵觀念:API 金鑰不等於身份驗證apikey Header 只是告訴 Gateway「你有權存取這個專案」,真正代表「你是誰」的是 Authorization: Bearer <JWT> 這個 Header 裡的 JWT。前者由 Gateway 驗證,後者則由後端服務(尤其是 PostgREST)拿去交給 Postgres 做 RLS 判斷。兩者常一起送出,但職責完全不同——這是後面實作與除錯時很常搞混的點。

各元件真面目與其開源專案

通過 Gateway 之後,請求會被送到對應的服務。Supabase 最迷人的地方在於:每一個雲端受管服務,背後都對應一個獨立、可自架的開源專案。下表把它們一次對齊:

服務(產品名)底層開源專案語言/技術GitHub 倉庫一句話職責
DatabasePostgreSQLCsupabase/postgres資料庫核心,唯一真相來源
REST APIPostgRESTHaskellPostgREST/postgrest把 schema 反射為 RESTful API
AuthGoTrueGosupabase/gotrueJWT 發行、OAuth、MFA
RealtimeRealtimeElixir/Phoenixsupabase/realtimeWebSocket、WAL 訂閱
StorageStorage APINode.jssupabase/storage-apiS3 相容物件儲存
Edge FunctionsDeno runtimeTypeScript/Denosupabase/edge-runtime全球邊緣執行函式
API GatewayKong / Envoy對應各自專案統一入口、路由、驗證
連線池SupavisorElixirsupabase/supavisor多租戶 Postgres 連線池

這張表回答了一個常見疑問:「Supabase 到底是不是黑箱?」不是。你在雲端點幾下就能用的功能,換到自架環境,其實就是把上面這幾個開源專案用 Docker Compose 拼起來(官方自架方案約 13 個服務)。理解這一點,你就不會把 Supabase 當成不透明的服務,而是「一組你隨時能看原始碼、能替換的積木」。

接著逐一認識這些積木:

PostgREST(自動 REST API,Haskell) 這是最能體現 Supabase 哲學的元件。PostgREST 讀取 Postgres 的 schema,自動把資料表、視圖、函式反射成一組 RESTful 端點,你完全不需要手寫任何後端。它的運作流程是:

  1. 讀取 Postgres schema,動態產生 HTTP 端點
  2. 收到請求後,把 HTTP 查詢翻譯成 SQL
  3. Authorization Header 取出 JWT,設定到 Postgres 的 session 變數
  4. Postgres 套用 RLS 政策,回傳過濾後的資料

它支援過濾(?status=eq.pending)、排序、分頁、透過外鍵自動 JOIN、以及呼叫預存函式(/rest/v1/rpc/函式名)。你之後用的 supabase-js 客戶端,底層打的就是 PostgREST 的端點。

GoTrue(身份驗證,Go) GoTrue 是一台以 Go 撰寫的 JWT 身份驗證伺服器,負責註冊、登入、Token 發行與更新。它支援 Email/密碼、Magic Link 無密碼登入、OTP、近 20 種 OAuth 社交登入、SAML 企業 SSO 與 MFA。重點在於它把使用者資料存在 Postgres 的 auth schemaauth.usersauth.sessions 等表),而不是另外一個資料庫。這代表你的業務資料表可以用外鍵直接指向 auth.users——這是 NoSQL 世界做不到的。它發行的 JWT 裡含使用者 UUID、Email、角色與自訂聲明,正是 PostgREST 拿去做 RLS 判斷的依據。

Realtime(即時訂閱,Elixir/Phoenix) Realtime 是以 Elixir/Phoenix 打造的 WebSocket 伺服器。它與其他服務最不同的地方在於:它不是靠 HTTP 查詢資料庫,而是直接讀取 Postgres 的 WAL(Write-Ahead Log,預寫日誌)。當資料表發生 INSERT/UPDATE/DELETE,變更會寫入 WAL,Realtime 透過邏輯複製監聽這些變更,再透過 WebSocket 推送給訂閱的客戶端。它提供三種機制:監聽資料表變更的 Postgres Changes、低延遲 pub/sub 的 Broadcast、以及追蹤在線狀態的 Presence。選用 Elixir 是因為 BEAM 虛擬機天生擅長維持海量並發連線。

Storage API(物件儲存,Node.js) Storage 提供 S3 相容的物件儲存,用來放圖片、影片、文件。它與純 S3 最大的差異在於存取控制走 Postgres 的 RLS:檔案的中繼資料(檔名、大小、MIME 類型、擁有者)存在資料庫的 storage schema,你可以用 SQL 查詢,也可以用 RLS 政策控制誰能上傳、下載。實際的檔案位元組存在物件儲存後端,但「誰能碰它」這件事由 Postgres 決定。

Edge Functions(邊緣函式,Deno) Edge Functions 是在全球邊緣節點執行的 TypeScript/JavaScript 函式,執行環境是 Deno(不是 Node.js)。它用來處理自動 API 表達不了的自訂邏輯,例如串接第三方金流、寄 Email、Webhook。它通常持有 service_role 金鑰、在後端環境安全地繞過 RLS 存取資料庫。

Supavisor(連線池,Elixir) 最後補上一個容易被忽略、但生產環境很關鍵的元件。Postgres 每個連線都需要一個後端行程,在 Serverless 或 Edge 場景下,每次請求都新建連線會迅速耗盡連線上限。Supavisor 是 Supabase 自研的多租戶連線池(取代舊版 PgBouncer),透過重用連線解決這個問題。它有 Session 模式(埠 5432)與 Transaction 模式(埠 6543),後者適合 Serverless,但不支援 Prepared Statements,用 ORM 時要特別留意。

資料如何以 Postgres 為單一真相來源

把上面拼起來,就能回答本篇最核心的問題:這麼多服務,資料怎麼保持一致? 答案是它們共用同一個 Postgres,只是各自寫進不同的 schema:

Schema由誰管理存什麼
public你(開發者)你的業務資料表(todos、orders、profiles…)
authGoTrue使用者、Session、身份提供者
storageStorage APIBucket 與檔案中繼資料
realtimeRealtime訂閱與發布設定

因為它們在同一個資料庫,你能做到跨 schema 的外鍵關聯與聯合 RLS 政策——例如業務表 todos.user_id 直接指向 auth.users.id。這就是「單一真相來源」的實際落地:使用者這個概念只存在一處,其他服務都以它為準,不需要在多個系統間同步。RLS 政策寫在資料庫層,因此無論請求走 REST、走 SDK、走 Realtime 還是 Edge Function,套用的都是同一套規則。

關鍵術語小結:

  • schema(結構描述):Postgres 用來組織資料表的命名空間。Supabase 各服務用不同 schema 共用同一個資料庫。
  • WAL(Write-Ahead Log,預寫日誌):Postgres 記錄所有資料變更的日誌,Realtime 靠讀它來推送即時事件。
  • JWT(JSON Web Token):GoTrue 發行、代表使用者身份的權杖,PostgREST 拿它做 RLS 判斷。
  • 連線池(Connection Pooling):Supavisor 重用資料庫連線,避免 Serverless 場景耗盡連線數。

實作範例

概念講完,我們用一個最能體現架構的實驗來收尾:建一張表,觀察同一份資料如何同時被「自動 REST API」與「supabase-js SDK」存取——兩條看似不同的路徑,其實都通過 Gateway,最終打到同一個 PostgREST、同一個 Postgres。

步驟一:用 SQL 建表,API 自動出現

在 Supabase Studio 的 SQL 編輯器貼上以下 SQL。建完表的那一刻,PostgREST 就已經自動幫它生成好 API 了,你不需要做任何額外設定。

-- 建立一張 notes 資料表
CREATE TABLE notes (
  id bigserial PRIMARY KEY,
  user_id uuid NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE,
  content text NOT NULL,
  created_at timestamptz DEFAULT now()
);

-- 開啟 Row Level Security(務必第一步就開)
ALTER TABLE notes ENABLE ROW LEVEL SECURITY;

-- 政策:使用者只能讀取自己的筆記
CREATE POLICY "Users can read own notes."
  ON notes FOR SELECT
  USING ( user_id = auth.uid() );

注意 user_id uuid REFERENCES auth.users(id) 這一行:業務表直接用外鍵指向 GoTrue 管理的 auth.users。這正是「單一真相來源」在程式碼裡的樣子——因為它們在同一個 Postgres,才能這樣關聯。

步驟二:用原始 REST API 查詢(PostgREST 直球)

先用最底層的方式,直接 curl 打 PostgREST 的端點,感受 Gateway → PostgREST → Postgres 這條路徑。注意這裡送了兩個 Header:apikey(給 Gateway 驗證)與 Authorization(帶 JWT,給 Postgres 做 RLS)。

# 直接呼叫自動生成的 REST 端點(毋須任何後端程式碼)
curl 'https://<你的專案ref>.supabase.co/rest/v1/notes?select=id,content&order=created_at.desc' \
  -H "apikey: <你的 anon key>" \
  -H "Authorization: Bearer <使用者的 JWT>"

# 回傳:只包含當前使用者的筆記(RLS 已在資料庫層過濾)
# [{"id":2,"content":"架構筆記"},{"id":1,"content":"第一則筆記"}]

步驟三:用 supabase-js SDK 查詢同一份資料

現在換成官方 SDK。你會發現寫法完全不同,但底層打的是同一個 PostgREST 端點、同一個 Gateway、同一個 Postgres——SDK 只是把 curl 那串 URL 與 Header 包裝得更好用。

import { createClient } from '@supabase/supabase-js'

// anon key 可安全放前端,因為真正的防線是 RLS
const supabase = createClient(
  'https://<你的專案ref>.supabase.co',
  '<你的 anon key>'
)

async function getNotes() {
  // 這行底層等同步驟二的 curl,走 /rest/v1/notes
  const { data, error } = await supabase
    .from('notes')
    .select('id, content')
    .order('created_at', { ascending: false })

  if (error) {
    console.error('查詢失敗:', error.message)
    return
  }
  console.log('我的筆記:', data) // 輸出:只有自己的筆記,RLS 自動過濾
}

getNotes()

這個實驗的重點是:REST 與 SDK 是同一個服務的兩張臉。你沒有寫任何後端,資料庫一變(新增欄位、改政策),兩條路徑的行為就一起跟著變。這就是「以 Postgres 為單一真相來源、由 PostgREST 自動反射」的威力。

常見錯誤與最佳實踐

初學者理解 Supabase 架構時,最常踩的幾個坑:

誤解一:「各服務有各自獨立的資料庫,我要在它們之間同步資料。」 不對,而且這是最關鍵的誤解。Auth、Storage、Realtime 通通寫進同一個 Postgres,只是佔不同 schema。你不需要「把使用者從 Auth 同步到業務資料庫」——它們本來就在同一個資料庫,直接用外鍵關聯 auth.users 即可。把架構想成「一個資料庫、多個視窗」,而不是「多個資料庫要對帳」。

誤解二:「有了 apikey 就代表通過身份驗證了。」 apikey 只是 Gateway 層的專案存取憑證,它不代表使用者身份。真正代表「你是誰」的是 Authorization Header 裡的 JWT。常見的除錯陷阱是:只送了 apikey 沒送 JWT,結果 RLS 把 auth.uid() 當成 NULL,查不到任何資料還以為是 bug。正確做法是理解這兩個 Header 職責不同,登入後要帶上使用者的 JWT。

誤解三:「service_role key 只是另一把比較方便的鑰匙。」 不是。service_role 金鑰完全繞過 RLS,等於資料庫超級管理員。它只能存在後端(Edge Functions、你的伺服器環境變數),絕不可出現在前端或 Gateway 對外的請求裡。一旦洩漏,整個資料庫門戶大開。

誤解四:「忽略連線池,Serverless 直接連資料庫就好。」 在 Serverless 或 Edge 場景下,每次請求新建直連會迅速耗盡 Postgres 的連線上限。正確做法是走 Supavisor 的 Transaction 模式(埠 6543),並記得它不支援 Prepared Statements,用 ORM(如 Prisma)時要對應調整設定。

最佳實踐總結:

  • 把架構理解成「一個 Postgres + 多個環繞它的獨立服務」,資料只有一份
  • 分清楚 apikey(Gateway 驗證)與 Authorization: Bearer JWT(RLS 身份)兩個 Header
  • 前端只用 anon key,service_role key 僅限後端
  • Serverless/Edge 走連線池(Supavisor Transaction 模式),別直連
  • 需要客製邏輯時用 Edge Functions,而不是把 service_role 塞進前端

小結

這篇是 Supabase 系列教學的第 002 篇,我們把上一篇《平台總覽》的架構圖徹底拆開:

  • API Gateway 是唯一入口:Kong/Envoy 負責金鑰驗證、路由、速率限制與 CORS,依路徑分流到各服務
  • 每個服務都是獨立的開源專案:PostgREST(Haskell)自動生成 REST API、GoTrue(Go)做身份驗證、Realtime(Elixir)讀 WAL 推送即時事件、Storage(Node.js)管物件儲存、Edge Functions(Deno)跑自訂邏輯
  • 受管即開源:雲端服務背後就是可自架的 GitHub 倉庫,用 Docker Compose 就能組起來
  • Postgres 是單一真相來源:各服務用不同 schema(public/auth/storage)共用同一個資料庫,靠外鍵與 RLS 保持一致
  • apikey 不等於身份:金鑰給 Gateway,JWT 給 Postgres 做 RLS,兩者職責分明

下一篇《雲端 vs 自架與定價》,我們會接著談:既然這些元件都是開源可自架的,那什麼時候該用 Supabase 雲端、什麼時候該自己用 Docker Compose 拉起這 13 個服務?雲端各方案(Free、Pro、Team)的定價與額度又該怎麼估算?把架構看懂之後,這些取捨會清楚許多。

跟著系列一路走,你會從「知道有哪些元件」進展到「知道它們怎麼協作、怎麼部署」。下一篇見。

BenZ Software Developer

熱愛技術的軟體開發者,在這裡分享程式開發經驗與學習筆記。

本週主打

AI 自動化入門包

你每天手動在做的那些煩事,其實 AI 可以自己跑。這份給你 10 個照著做就會的自動化工作流 + 50 個複製即用的提示詞,不用會寫程式。

看看這個產品 →