Backend-as-a-Service 是一个捆绑包:认证、数据库、存储、函数、实时推送,全部隐藏在一个 SDK 背后。你买的不是数据库,而是永远不必操心底层如何托管的权利。Firebase 和 Supabase 是两个默认选项——数据模型不同,迁出成本不同,定价曲线相近。本文教你如何做出选择。
"BaaS"——backend-as-a-service——这个类别名称,实际上代表着对应用程序构建方式的一种押注:一个典型的应用需要认证、数据库、文件上传,以及让这些模块通过网络互相通信的机制。传统做法是自己把这些拼起来——Postgres 存数据、Passport / NextAuth 做认证、S3 做存储、一个 API 服务器做粘合,再加三四个配置文件让它们保持同步。BaaS 的押注是:接受某家厂商预先组装好的四合一方案,能让你更快地把同样的产品交付出去。你写的基础设施代码更少,认证开箱即用,SDK 就是你要 import 的一切。代价是:厂商掌控了数据模型和 API 的形态。
Firebase(Google,2011 年创建,2014 年收购)是历史更悠久的选项。它的数据库是 Firestore——一个内置实时同步的 NoSQL 文档存储,你把 JSON 格式的文档写入集合,SDK 会把更新推送到每个正在查看的客户端。Supabase(独立公司,2020 年创建)是"Firebase,但用 Postgres"——同样封装了认证、存储、实时推送和函数,但底层数据库是真正的 Postgres,支持 SQL、JOIN 和外键。这个区别看起来不大,却是在这个技术栈上你要做的最重要的决策:Firestore 优化的是"在海量客户端上实时展示符合条件的文档";Postgres 优化的是"让你在日后想到任何问题时都能查到答案"。选择与你写查询的方式相匹配的数据模型。
本教程涵盖决策过程(这是最主要的工作)、真正重要的几个维度(大多数清单都会漏掉它们),以及两者各自可运行的"读取一行数据 + 检查登录状态"hello-world 示例。读完之后,你会知道该选哪个,也会验证基本功能在你的选择下确实可以运行。从 hello-world 到真实应用,之后的工作不过是同一套 SDK 的延伸。
Firebase 和 Supabase 都将相同的五项能力封装在一个 SDK 和一套凭据之后:
你在托管费之外真正为之付费的,是所有五项都绑定到同一套用户身份这件事。已登录用户的照片上传(存储)之所以被允许,是因为数据库里有一条行级别的规则,引用了他们的认证令牌(认证)。自己搭建意味着要手动编写这套连接逻辑;用 BaaS,SDK 直接把接好线的版本交给你。
Firebase 和 Supabase 之间最重要的区别就是数据库。选错了,之后每新增一个功能都是在跟 schema 较劲。
Firestore(Firebase)——NoSQL 文档。数据存储在由文档组成的集合中,文档就是 JSON。没有 JOIN,没有外键,没有迁移——schema 只存在于你的代码里。查询很简单("这个集合中字段 X 等于 Y 的文档"),在大规模场景下非常快,但任何涉及多表的操作(类似 SQL JOIN)要么需要把数据反范式化写进文档,要么在客户端拉取多个集合后自行处理。实时同步默认对所有读取开启,这是 Firebase 的招牌特性。
Postgres(Supabase)——关系型行。数据存储在带有类型列和预定义 schema 的表中。JOIN 存在,外键存在,SQL 是查询语言;Supabase SDK 在上面封装了一个查询构建器,但你也可以在需要时直接写原生 SQL。schema 变更通过迁移文件管理——存放在仓库中的文本文件,负责将数据库从一个形态演变到另一个形态。实时功能需按表选择开启。
有一条经验法则出奇地管用:如果你的应用感觉像"一个有评论和点赞的帖子流",Firestore 就够了。如果感觉像"用户、项目、任务、分配,加上跨所有数据的报表",你会跟 Firestore 不断较劲,Postgres 才是赢家。大多数真实的消费者应用,即便最初看起来像前者,经过一年的迭代都会演变成后者的形态。这种倾向是 2026 年团队将 Supabase 作为默认选择的主要原因。
| 维度 | Firebase | Supabase |
|---|---|---|
| 查询能力 | 有限:只能在一个字段上过滤,一个有索引的字段上做等值或范围查询。不支持 JOIN,不支持 GROUP BY。 |
完整 SQL。支持 JOIN、窗口函数、递归 CTE、全文搜索。 |
| Schema 变更 | 无。随时添加字段;数据是否有效完全取决于应用代码。 | 迁移文件(存放在仓库中的文本文件,应用到数据库)。工作量更大,但更安全。 |
| 认证提供商 | 邮箱、手机、Google、Apple、Facebook、Twitter、GitHub、匿名、自定义 JWT。 | 邮箱/密码、魔法链接、Google、Apple、GitHub、GitLab、Bitbucket、Slack、Discord、Twitter、Microsoft、Notion、Spotify、Twitch、匿名、手机、SAML。 |
| 实时推送 | 默认对所有读取开启,是产品的核心亮点。 | 按表选择开启;底层使用 Postgres 逻辑复制。稳定,但不是默认行为。 |
| 行级别安全 | Firestore 安全规则——一套小型 DSL。功能强大,但是专有的。 | Postgres RLS——SQL。功能强大、可移植,是业界标准。 |
| 迁出成本 | 高。数据以 Firestore 专有格式存储;虽然有导出工具,但在其他技术栈上重建应用意味着几乎要重写所有查询。 | 低至中等。数据是 Postgres——pg_dump 后可以在任意地方恢复。认证和存储是厂商专有的,但通常掌握最大筹码的数据库是可移植的。 |
| 本地开发 | Firebase Emulator Suite——在本地运行 Firestore、Auth、Functions。成熟、文档完善。 | Supabase CLI——在 Docker 中本地运行 Postgres + Auth + Storage + Studio。成熟、文档完善。 |
| 自托管 | 不支持。Firebase 是仅限 Google 的服务。 | 支持。Supabase 是开源的;你可以在自己的 Postgres + 认证服务器上运行整套系统。大多数人不这么做,但你可以。 |
| 厂商锁定(主观) | 显著。心智模型和查询方式无法迁移。 | 适中。Postgres 技能和 SQL 可以迁移;Supabase 专有的辅助方法则不能。 |
迁出成本这一行比大多数人预想的更重要。成功的应用会产生当初选择 BaaS 时没有预料到的新需求。用 Supabase,"我需要迁出"意味着"pg_dump,然后让应用连接另一个 Postgres"。用 Firebase,则是一个以月为单位计算的项目。
两者都有慷慨的免费层,足以支撑个人项目和小型 SaaS。两者都会在超出配额时切换到付费计划,然后在基础计划之上按用量计费。
Firebase(Spark → Blaze)。Spark 计划免费,包含 1 GiB Firestore、1 GB 存储、10 GB 传输、每日 5 万次 Firestore 读取、2 万次写入。超出后切换到 Blaze(按量付费):每 10 万次 Firestore 读取 $0.06,每 10 万次写入 $0.18,存储 $0.026/GB 每月。那些臭名昭著的"Firebase 账单爆炸"故事,通常是在 Blaze 计划下配置有问题的客户端陷入无限读取循环——计费器不会停。在上线前务必在 GCP 项目中设置每日预算告警。
Supabase 免费层 → Pro。免费层包含 500 MB Postgres 数据库、1 GB 存储、每月 5 万活跃用户、5 GB 出口流量。Pro 是固定 $25/月加用量——8 GB 数据库、100 GB 存储、10 万 MAU、250 GB 出口流量。Pro 以上是企业定价。定价更偏向"订阅制"而非 Firebase 的纯按量计费;账单不会意外暴涨,但计划升级需要主动操作。
对于约 1 万月活用户、数据量适中的真实应用:两者在入门付费计划下都在 $25–60/月左右。曲线在此之上开始分叉——Firebase 随流量平滑扩展;Supabase 在计划边界有阶梯式变化(Pro → Team → Enterprise)。
一次性配置:打开 Firebase 控制台 → 添加项目。进入项目后:构建 → Authentication → 启用 Google。构建 → Firestore Database → 在测试模式下创建。从项目设置中复制 Web 配置对象(包含一个 apiKey 和若干 ID)。
在你的项目中:
npm i firebase
创建 src/firebase.js:
import { initializeApp } from "firebase/app";
import { getAuth, GoogleAuthProvider, signInWithPopup } from "firebase/auth";
import { getFirestore, doc, getDoc } from "firebase/firestore";
const app = initializeApp({
apiKey: "AIza...",
authDomain: "your-project.firebaseapp.com",
projectId: "your-project",
// ...rest of config
});
export const auth = getAuth(app);
export const db = getFirestore(app);
export const signInWithGoogle = () =>
signInWithPopup(auth, new GoogleAuthProvider());
export async function getProfile(uid) {
const snap = await getDoc(doc(db, "profiles", uid));
return snap.exists() ? snap.data() : null;
}
在你的组件中:
import { useEffect, useState } from "react";
import { auth, signInWithGoogle, getProfile } from "./firebase";
import { onAuthStateChanged } from "firebase/auth";
export default function App() {
const [user, setUser] = useState(null);
const [profile, setProfile] = useState(null);
useEffect(() => onAuthStateChanged(auth, async (u) => {
setUser(u);
if (u) setProfile(await getProfile(u.uid));
}), []);
if (!user) return <button onClick={signInWithGoogle}>Sign in</button>;
return <div>Hi {user.displayName} — profile: {JSON.stringify(profile)}</div>;
}
运行它。点击登录按钮,弹出 Google OAuth 授权窗口;授权后,页面会用你的名字向你打招呼。在 Firestore 控制台中向 profiles/<your-uid> 写入一个文档;刷新页面,数据就出现了。这就是 30 行代码里的整个捆绑包。
一次性配置:打开 Supabase 控制台 → 新建项目。等待约 90 秒让 Postgres 实例启动。在 Authentication → Providers 中启用 Google(粘贴来自 GCP 的 Google OAuth 客户端 ID 和密钥)。在 SQL Editor 中创建一个 profiles 表:
create table profiles (
id uuid primary key references auth.users on delete cascade,
display_name text,
created_at timestamptz default now()
);
alter table profiles enable row level security;
create policy "own profile" on profiles for select using (auth.uid() = id);
从项目设置中复制 SUPABASE_URL 和 SUPABASE_ANON_KEY。
在你的项目中:
npm i @supabase/supabase-js
创建 src/supabase.js:
import { createClient } from "@supabase/supabase-js";
export const supabase = createClient(
"https://<PROJECT>.supabase.co",
"eyJ...ANON_KEY"
);
export const signInWithGoogle = () =>
supabase.auth.signInWithOAuth({ provider: "google" });
export async function getProfile(userId) {
const { data, error } = await supabase
.from("profiles")
.select("*")
.eq("id", userId)
.single();
return error ? null : data;
}
在你的组件中:
import { useEffect, useState } from "react";
import { supabase, signInWithGoogle, getProfile } from "./supabase";
export default function App() {
const [user, setUser] = useState(null);
const [profile, setProfile] = useState(null);
useEffect(() => {
supabase.auth.getSession().then(({ data }) => setUser(data.session?.user ?? null));
const { data: sub } = supabase.auth.onAuthStateChange(async (_e, session) => {
const u = session?.user ?? null;
setUser(u);
if (u) setProfile(await getProfile(u.id));
});
return () => sub.subscription.unsubscribe();
}, []);
if (!user) return <button onClick={signInWithGoogle}>Sign in</button>;
return <div>Hi {user.email} — profile: {JSON.stringify(profile)}</div>;
}
运行它。流程完全相同:点击登录,Google OAuth 弹窗,页面向你打招呼。将你的 auth.users.id 作为主键插入一行到 profiles 表中;刷新,数据出现。结构与 Firebase 几乎相同,底层数据库换成了 Postgres。
"两者都用"是罕见但合理的选择——用 Firebase Auth + Firestore 处理应用的实时侧,用 Supabase Postgres 处理关系型数据侧。大多数团队发现这样的复杂度得不偿失,最终还是选择一个。
你不需要实时功能,不需要认证即服务,数据结构也很简单。一个部署在 Vercel 上、连接 Neon Postgres 和 NextAuth 的 Next.js 应用,比任何一个 BaaS 都简单得多。"五项能力合一 SDK"的前提假设是你全都需要;但实际上你往往不需要。
你已经深度使用 AWS 或 Azure。AWS Cognito + RDS + S3(或 Azure AD B2C + Cosmos + Blob)能实现同样的功能,而且使用你已有的提供商集成。引入 Firebase 或 Supabase 意味着多一家供应商的账单,以及多一个故障半径。
你无法接受两者的数据驻留位置或合规姿态。Firebase 运行在 Google Cloud 上;Supabase Cloud 运行在 AWS 上(Team 及以上版本支持多区域)。对于严格的本地部署需求,两者都不是正确答案;自托管 Supabase 或自建服务器上的 Postgres 模式(参见在服务器上安装 Postgres 和 Redis)才是。