
Supabase这个项目我用了一年多踩了不少坑也沉淀了不少经验。这东西本质上是一个开源的 Firebase 替代品核心数据库是 PostgreSQL通过一套封装好的工具链把后端常用的能力全部打包REST API、实时订阅、认证授权、对象存储、边缘函数、数据库管理后台。没有后端经验的团队可以直接拿来当后端用有后端经验的团队更可以把它当作一套自带管理界面的 Postgres 封装来细用。这篇文章我按自己的实操路线来写从概念拆到落地部署把它讲透。1. Supabase 核心概念与整体设计思路1.1 先弄明白BaaS 解决的是什么问题BaaSBackend as a Service不是新鲜概念它在移动互联网时代就出现过。核心思路是把后端常见的能力模块化、托管化让前端团队不需要自己维护服务器、不需要写鉴权逻辑、不需要搭建实时通道拿到一个 SDK 直接对接底层服务。传统模式下前端要对接后端得先走接口约定、联调、部署流程一个小改动可能要牵动整个团队。BaaS 想消除的就是这层摩擦。Supabase 在 BaaS 里比较特殊的地方在于它没有自研一套存储引擎而是直接选择了 PostgreSQL。这个选择的影响非常深远。PostgreSQL 是开源数据库里的老牌选手支持复杂查询、事务、视图、触发器、外键生态里有一堆现成的工具。这意味着 Supabase 不只是帮你把数据存起来、开几个接口而是让你保留了完整的关系型数据库能力。你可以用原生的 SQL 建立复杂的查询视图可以用触发器做跨表数据同步可以在应用层之外直接操作数据。我用 Supabase 之前试过其他 BaaS最大的障碍是数据逻辑没法深入定制。做一个业务系统总有几天会碰到“这个接口要串三张表还带条件过滤”的需求普通的 NoSQL BaaS 遇到这种场景就会很痛苦。Supabase 因为底层是 Postgres天然就支持 join、子查询、窗口函数。你可以完全按照传统后端思维设计表结构只不过最终暴露给客户端的是 REST API。1.2 Supabase 全家桶的组件构成Supabase 不是一个单一服务它是一整套开源组件的集合。从使用角度看主要包含下面几个部分PostgreSQL 数据库所有数据的最终存储地是整套系统的心脏。PostgREST把数据表自动映射成 REST API 的中间层负责处理 HTTP 请求到 SQL 的转换。GoTrue认证服务处理注册、登录、第三方 OAuth、Token 签发支持 JWT。Realtime基于 PostgreSQL 的逻辑复制实现数据变更推送让前端可以实时订阅表变化。Storage对象存储服务基于 S3 协议用来存图片、文件、任意静态资源。Edge Functions基于 Deno 运行时的边缘函数服务用来跑自定义后端逻辑类似小程序云函数。StudioWeb 管理后台直接操作表结构、编写 SQL、创建策略、看日志。这套架构最大的特点是“组合式”每层都可以单独研究。PostgREST 本身是一个独立的开源项目GoTrue 也可以单独跑。Supabase 把它们打包装在一起配合漂亮的 UI 和 CLI 工具降低了整体使用门槛。1.3 为什么这种组合更符合实际开发习惯我自己的体会是Supabase 降低了两个门槛一个是数据库操作门槛一个是服务部署门槛。数据库操作门槛的降低很直观。以前我维护一个 MySQL 或 PostgreSQL每次要导出数据得敲命令行要看数据结构得用 Navicat要加字段得写 ALTER TABLE。Supabase Studio 把这些全部做成了可视化操作鼠标点几下就能完成建表、加字段、建立外键关联甚至能直接在浏览器里执行 SQL。前端团队不用再依赖后端同学“帮忙开个接口”自己就能搭出完整的数据结构。服务部署门槛的降低更关键。托管版的 Supabase 就是直接注册、建项目、拿连接串几分钟就能跑起来。自托管版则给了你完整的 Docker Compose 文件拉下来就能在自己服务器上跑一整套餐。这两个选择覆盖了不同阶段的需求初期用托管版验证业务后期有合规或成本要求时迁移到自托管。我认识的一些团队就是用这种路径走下来的业务验证期几乎没花什么钱。2. 核心细节解析与实操要点2.1 数据表设计从业务需求到数据结构Supabase 的表设计规则跟传统关系型数据库完全一致这也意味着你之前积累的设计经验可以直接复用。第一步永远是梳理业务对象之间的关系。比如做一个简单的博客系统至少会有用户表、文章表、评论表文章跟用户是多对一评论跟文章是多对一用户跟评论是多对一。这些关系用外键表达Supabase 的 Studio 里可以直接在“关系”标签页看到关联图谱。建表的时候有几点需要注意。第一每一张表最好都带一个id字段类型用uuid默认值填gen_random_uuid()。不要用自增整数当主键因为一旦数据公开暴露别人可以通过 id 递增判断你的业务量而且合并数据、做分表迁移时整数 id 非常麻烦。第二时间字段建议直接使用created_at和updated_at类型取timestamptz默认值now()。很多 ORM 框架会自动处理这类字段在 SQL 层面设置默认值更保险。第三尽量给常用的过滤字段建索引。Postgres 默认会对主键、外键建索引但如果你经常按某个业务字段查比如按文章分类查文章就得手动建索引。所有表建完之后还要考虑数据软删除的问题。业务系统里直接物理删除数据存在风险后面出问题没法追溯。我一般会加一个deleted_at字段查询接口里用视图把所有带deleted_at IS NULL条件的数据包装一层这样应用的逻辑层就不用处处关注删除了。Supabase 支持创建视图视图会同样被 PostgREST 映射成可查询的“类表”可以直接通过 API 访问。2.2 Row Level Security权限控制的真正核心很多人第一次接触 Supabase 时容易忽略 RLS结果把数据表暴露在公网上甚至能直接写库这是非常危险的。RLSRow Level Security是 Postgres 原生提供的功能在某张表上开启策略定义哪些行对哪些用户可见、可改。PostgREST 调用 SQL 时会带上当前请求的auth.uid()上下文RLS 策略就能根据这个值做判断。开启 RLS 的方式很简单建表后在 Studio 里打开“启用行级安全”的开关默认所有操作都会被拒绝然后按需添加策略。举个例子文章表posts的策略可以是这样对于select操作允许所有人查看published_at IS NOT NULL的记录。对于insert操作只允许auth.uid() author_id的记录插入。对于update操作只允许作者本人更新自己的文章。对于delete操作只允许作者本人删除。这里面的关键点在于表结构设计和 RLS 策略设计是一体两面。你写出表结构时可能觉得简单但 RLS 策略写得不好应用上线后就会出现各种权限漏洞或者查询空白。我建议每个表的策略都至少覆盖全部 DML 操作不要用默认关闭策略来逃避问题因为你会忘记后面还有哪些地方在调这些接口。RLS 策略的性能也是一个可能踩坑的地方。策略里的条件如果涉及函数调用例如auth.uid()Postgres 执行计划可能会逐行扫描数据量大了之后性能会明显下降。解决办法是在条件列上建索引。比如author_id这种字段几乎所有策略都会用到一定要建索引。2.3 认证系统的设计思路认证永远是 BaaS 里最复杂的部分。Supabase 的认证系统基于 GoTrue支持邮箱密码、手机验证码、第三方 OAuth。它签发的 JWT 默认包含三个关键信息sub用户 ID、role角色、aud受众。这个 JWT 会作为访问 API 时 Bearer Token 传入PostgREST 会校验签名和过期时间RLS 策略会读取里面的sub作为auth.uid()。邮箱注册激活的行为需要仔细斟酌。默认设置里用户注册后需要确认邮箱才能登录验证邮件的链接要指向你的前端页面而不是 Supabase 默认页面。如果你不配置这个用户点击邮件链接后可能跳到 Supabase 的提醒页面体验很差。需要在认证设置里把“确认邮件”的 URL 改为自己前端应用的回调地址并且在前端处理email_confirmed状态。另外还要设计好密码重置流程。Supabase 重定向链路需要配置得足够健壮用户点邮件里的链接会带recovery_token跳转到你的前端页面前端需要用这个 token 调用supabase.auth.verifyOtp然后才能更新密码。这个流程如果不完整密码找回功能基本是废的。如果你需要更细粒度的权限控制比如不同角色的人登录后看到的数据范围不同建议在用户表里加一个role字段然后在 RLS 策略里判断auth.jwt() - role或者直接判断这个用户 ID 在用户表中的角色字段。前者是 JWT 自带角色后者每次查库更灵活适合角色经常变化的场景。2.4 实时订阅的机制Supabase Realtime 的原理是监听 Postgres 的 WAL预写日志当某张表有 INSERT、UPDATE、DELETE 操作时把变更推给所有订阅了该通道的客户端。这个能力和原来的“轮询接口拿最新数据”完全不同是一次真正的推送式更新。启用实时订阅需要在数据库里创建 publication并把它绑定到需要监听的表上。Studio 里可以在“数据库”-“发布”里创建或者直接执行 SQL-- 创建一个绑定到特定表的 publication create publication supabase_realtime add table posts, comments;前端订阅的写法也很直观用 supabase-js 的 channel 接口监听 Postgres 变更事件const channel supabase .channel(posts-changes) .on( postgres_changes, { event: INSERT, schema: public, table: posts }, (payload) { console.log(收到新文章:, payload.new) updateUI(payload.new) } ) .subscribe()实时功能要慎用因为它是长连接每个客户端都会占用连接资源。Supabase 托管版的连接数有空闲限制并发在线用户太多时连接可能被拒需要做一层网关或队列来缓冲。业务上没必要做实时同步的数据就别开 publication控制资源消耗。3. 实操过程与核心环节实现3.1 项目创建与环境配置我以最常见的“前托管、后自托管”迁移路径为例来讲操作。先在 supabase.com 上注册账号创建一个新项目设置数据库密码时要注意这个密码是数据库超级管理员的密码不是应用连接用的密码。应用连接时用的是角色anon或authenticated密钥可以在 API Settings 里找到。项目创建后第一件事就是把Project Settings - API页面里的 URL 和 anon key 记录下来这两个值会写进前端项目。使用托管版时postgrest 服务、realtime 服务都在同一个域名变体下通了认证后接口请求会自动带上身份。建议不要把这些配置硬编码在业务代码里而是放进.env.local环境变量避免上传到公开仓库泄露。本地开发时也可以在本地用 Docker 起一套 Supabase 开发环境。官方提供的 CLI 工具supabase可以从零搭建本地开发栈执行supabase init之后supabase start即可启动所有容器。本地环境有独立的 Studio、数据库和认证模拟服务开发调试不会影响线上的数据。我通常的做法是本地环境搞定所有 DDL 和 RLS 策略再把 SQL 脚本同步到托管项目。3.2 前端接入和核心代码实现supabase-js 库的接入非常直接。安装依赖后创建客户端整个前端应用就可以通过它访问后端的全部能力了。一个简单的初始化代码如下import { createClient } from supabase/supabase-js const supabase createClient( process.env.NEXT_PUBLIC_SUPABASE_URL, process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY ) export default supabase注册和登录逻辑需要区分情况。普通的应用密码登录流程调用signUp后处理验证邮件链接回跳然后signInWithPassword。第三方 OAuth 登录时要区分 Web 端和移动端移动端可能需要配置自定义 URL schemeWeb 端则要设置允许的 callback 地址列表。登录成功后前端就自动带上用户身份访问表数据。举例获取当前用户自己的文章列表const { data, error } await supabase .from(posts) .select(id, title, created_at) .eq(author_id, user.id)如果 RLS 配置只允许用户看到自己的数据那么即使用户通过 API 控制台手动改查询条件也拿不到别人的数据。这正是 RLS 的意义所在API 层天然信任 RLS 的过滤结果任何数据访问都被限制在策略允许的范围内。如果业务里有比较复杂的数据聚合需求比如后台要按月统计文章发布数量建议不要在应用层做聚合而是直接在 Postgres 里创建视图。PostgREST 对视图的映射和表一致你可以对这个视图继续配置 RLS 策略前端用同样的 select 语法去查。这样做既免去了在后端写 Count 接口的工作又保持了查询性能。3.3 用外部工具连接数据库Supabase Studio 的 SQL 编辑器和表编辑器很方便但复杂的数据导