返回博客列表
技术文章Supabase微信小程序架构设计

微信小程序接入 Supabase 全解析

2025年05月🇨🇳 中文

如果你还没看系列导言,建议先从这里开始,理解这套方案的信任边界与取舍:/blogs/supabase/00-why-supabase-for-miniprogram-backend

这一篇只解决一个问题:小程序端到底怎么“可靠地”连上 Supabase。 这里的“可靠”不是指“能跑起来”,而是指你把它接进真实项目后,不会因为 SDK 环境不兼容、token 续期、合法域名、Realtime 依赖这些细节反复返工。

这篇文章的产物是什么

读完并照做后,你应该得到三样东西:

  1. 一个在小程序环境可运行的 Supabase Client(基于 supabase-wechat-stable-v2
  2. 一个最小封装层:登录态缺失时快速失败、token 过期时自动续期并重试
  3. 一张“兼容性边界清单”:哪些能力稳定可用、哪些是实验/不推荐、出问题先查哪里

兼容性口径(先把边界说清楚)

在微信小程序里,我对 Supabase 的能力划分是这样的:

模块小程序可用性备注
Database / PostgREST稳定可用前提是 RLS 写清楚
Auth稳定可用微信登录需要 Edge Function 适配(见 02)
Storage稳定可用底层走 wx.uploadFile,要处理合法域名与返回值差异
Realtime不建议默认开启依赖 phoenix,在小程序构建链路上有兼容风险;建议先按需评估,再决定是否投入适配成本

为什么官方 SDK 无法运行

@supabase/supabase-js 是为浏览器和 Node.js 设计的,它依赖的每一个底层 API 在微信小程序的逻辑层里都不存在。

微信小程序运行在一个双线程沙箱中:渲染层(Webview)负责 UI,逻辑层(JSCore/V8)负责 JavaScript 执行。两者通过消息机制通信,逻辑层被刻意隔离在一个没有 DOM、没有标准浏览器 API 的环境里。这个设计不是疏忽,是微信的主动选择——统一网络请求审计、防止 DOM 操作、保证沙箱安全。

SDK 的四个断点分别是:

  1. fetch 不存在。 postgrest-jsauth-js 的所有 HTTP 调用都通过 globalThis.fetch 发出。小程序没有 fetch,运行即抛 ReferenceError。你可以写一个基于 wx.request 的 polyfill,但这只是第一层问题,后面还有三个。

  2. localStorage 不存在。 auth-jslocalStorage 持久化 session。小程序的持久化 API 是 wx.getStorageSync / wx.setStorageSync,两套接口完全不同。auth-js 提供了 storage 自定义配置项,理论上可以注入适配器解决,但这需要深入 SDK 内部,维护成本高。

  3. WebSocket 构造函数不兼容。 realtime-js 用标准 new WebSocket(url) 建连接,小程序的入口是 wx.connectSocket(),返回 SocketTask 对象。SocketTask 的接口与标准 WebSocket 完全不同——事件绑定是函数调用(task.onMessage(fn))而不是属性赋值(ws.onmessage = fn)。

  4. FormData / Blob 残缺。 storage-jsFormData 上传文件,小程序文件上传必须走 wx.uploadFile,这是两套完全不同的上传机制。

四个断点加在一起,靠临时 polyfill 把官方 SDK “凑合跑起来”通常不划算:维护成本高,而且很多问题会被推迟到真机或线上才暴露。


supabase-wechat-stable-v2:已封装好的适配层

理解了四个断点之后,你可以自己实现适配层,但更现实的路径是先用一个社区维护的现成方案:supabase-wechat-stable-v2

这个包的本质是将 @supabase/supabase-js 的四个底层依赖全部替换为微信等价实现:

官方 SDK 依赖替换为
fetchwx.request
localStoragewx.getStorageSync / wx.setStorageSync
WebSocket 构造函数wx.connectSocket
FormData 上传wx.uploadFile

替换完成后,对外暴露与官方 @supabase/supabase-js v2 基本一致的 API——createClientfrom().select().eq()authstorage 等写法不变,只是底层网络层换成了微信专有 API。

安装与构建 npm

npm install supabase-wechat-stable-v2

安装后,必须在微信开发者工具中执行「工具 → 构建 npm」,将 node_modules 中的包编译到 miniprogram_npm 目录。微信小程序无法直接引用 node_modules,必须经过这一步构建。

phoenix 的兼容性问题与修复

supabase-wechat-stable-v2 依赖链中有一个隐患:

supabase-wechat-stable-v2
  └── @supabase/realtime-js
        └── phoenix(Phoenix Channel 的 JS 实现)

什么是 Phoenix? Phoenix 最初是一个基于 Elixir 语言的著名 Web 框架,以其处理超高并发的 WebSocket 实时通信能力(Phoenix Channels)而闻名。Supabase 后端的 Realtime(实时推送)服务就是基于 Elixir 和 Phoenix 构建的。因此,Supabase 的前端 JS SDK 必须引入 phoenix 这个 npm 包(它的 JS 客户端),才能使用约定的协议与后端建立 WebSocket 通信。

在小程序中会用到吗?

  • 绝大多数普通应用用不到。 如果你的小程序只做常规的增删查改(Database)和登录注册(Auth),并不会用到 Realtime,自然也就不需要跑 phoenix 的代码。
  • 仅在特定强实时场景需要。 只有当你要做“聊天室”、“实时数据大屏推送”、“多人在线协同协作”这类需要“数据库一有变化立马主动推给前端”的功能时,才会触发 Supabase Realtime 和 phoenix

phoenix v1.x 的 CJS 文件在某些版本里会包含微信 JS 解析器不支持的语法,导致「构建 npm」时报错。

如果在小程序中不使用 Supabase Realtime,可以在 postinstall 阶段把 phoenix 入口替换为空 stub,让依赖链可以通过构建。这种做法的含义很明确:Realtime 相关能力在项目里应该被视为“不可用/未启用”,不要在业务里依赖它。

如果你的项目需要使用 Realtime,则不能使用这个 patch,需要寻找兼容微信 JS 引擎的 phoenix 版本。


三种接入路径的本质权衡

路径 A:REST 裸调。 直接用 wx.request 调用 PostgREST REST API,不引入任何 SDK。零依赖,完全可控,但你需要手动处理所有认证逻辑、错误格式和查询参数的序列化。适合接口调用极少的轻量项目,一旦项目规模增长,维护成本急剧上升。

路径 B:使用 supabase-wechat-stable-v2 安装这个包后,调用方式与官方 SDK 完全一致,无需自己实现适配层。可以在此基础上再包一层业务封装,处理认证拦截和 token 刷新逻辑。

路径 C:云函数代理。 所有 Supabase 请求经微信云函数或自建 Node.js 服务转发,服务端使用官方 SDK。优点是可以用 SDK 的完整功能,缺点是每次请求多一跳延迟,实时功能(Realtime)很难通过代理透传,架构复杂度明显上升。

路径 B 通常是更省事的起点:先解决“能跑”,再逐步补工程化收口。至于是否要走到路径 C(云函数代理),取决于你的业务是否强依赖官方 SDK 的某些能力,以及你是否愿意为多一跳请求承担长期成本。


PostgREST 协议:理解它才能封装好

Supabase 的数据库 API 不是 Supabase 自己设计的,而是 PostgREST——一个将 PostgreSQL schema 自动映射为 RESTful HTTP API 的开源项目。理解 PostgREST 的协议设计,是封装好适配层的前提。

PostgREST 的核心思路是:HTTP 动词对应 SQL 操作,URL 参数对应查询条件,HTTP Header 携带行为控制指令

具体映射关系:

HTTPSQL说明
GET /tableSELECT查询,条件通过 URL 参数传递
POST /tableINSERT插入,数据在 request body
PATCH /table?filtersUPDATE ... WHERE更新指定行
DELETE /table?filtersDELETE ... WHERE删除指定行
POST /rpc/function_nameSELECT function_name(...)调用数据库函数(RPC)

查询参数遵循 列名=操作符.值 的格式。例如 ?status=eq.published 对应 WHERE status = 'published'?created_at=gte.2024-01-01 对应 WHERE created_at >= '2024-01-01'。这个设计让 URL 具备了表达完整 SQL WHERE 子句的能力,同时保持 RESTful 风格。

Prefer 这个 Header 是 PostgREST 的行为控制开关,最重要的两个值:

  • Prefer: return=representation:让写操作(INSERT/UPDATE/DELETE)返回操作后的完整行数据,而不是默认的 HTTP 204 空响应
  • Prefer: resolution=merge-duplicates:配合 POST,在主键冲突时执行 UPDATE(实现 upsert 语义)

理解了这一层,封装的逻辑就清晰了:适配层需要做的事情是把链式 API 调用翻译成符合 PostgREST 协议的 HTTP 请求


最小封装:把认证缺失与 token 续期收口

supabase-wechat-stable-v2 解决的是“SDK 在小程序里能跑”。但真实项目里你很快会遇到两个工程问题:

  1. 未登录时,很多表根本不应该发请求(浪费网络 + RLS 拒绝的体验不统一)
  2. token 过期时,如果不自动续期并重试,业务层会被大量“401 / jwt expired”噪声淹没

我建议把这两件事收口成两个入口:supabase(查询入口)和 supabaseCall(带续期的执行器)。

supabase:统一查询入口(接口约定)

下面是我在项目里用的接口形状(你可以按自己的目录结构调整):

type UnauthenticatedError = { code: 'UNAUTHENTICATED'; message: string }

type FromOptions = {
  skipAuthCheck?: boolean
}

export type SupabaseWrapper = {
  from: (table: string, options?: FromOptions) => any
  auth: any
  storage: any
}

核心点是:from() 默认要求已登录;对少数公开数据才用 skipAuthCheck 放行。

supabaseCall:token 过期自动续期与重试(接口约定)

type SupabaseCallResult<T> = { data: T | null; error: any | null }

export async function supabaseCall<T>(
  fn: () => Promise<SupabaseCallResult<T>>
): Promise<SupabaseCallResult<T>> { }

这里最重要的一点是:fn 必须是函数,而不是已经执行的 Promise。因为你要在 token 刷新后“重新执行同一个查询”,必须重新发起一次请求。


关键实现细节

认证 Header 的动态注入。 supabase-wechat-stable-v2 内部通过 supabase.auth.setSession() 管理 session 状态,每次请求时自动从内存中读取当前 session 的 access_token 注入 Authorization Header。这意味着调用 setSession() 是同步 SDK 状态的关键动作——登录成功后、token 刷新后,都需要调用 setSession() 告知 SDK 当前的 token,否则 SDK 的内部状态会与 wx.storage 中的 token 不一致。

合法域名配置。 小程序调用外部 API 前,必须在微信公众平台的「开发设置 → 服务器域名」中添加 Supabase 项目的域名。requestsocketuploadFiledownloadFile 四类都要单独配置。本地开发可以临时勾选“不校验合法域名”,但真机与线上必须按四类补齐。


常见报错速查(优先排这几个)

  • 构建 npm 时报 Unexpected token / export 相关语法错误:优先检查依赖链里是否触发了 phoenix 的不兼容版本;确认是否需要 Realtime
  • 真机上请求失败但开发工具没问题:先排合法域名四类是否填全;其次排 HTTPS 证书与跨域跳转
  • PGRST301 / jwt expired:token 过期且没有自动续期,或续期后未 setSession 同步 SDK 内部状态
  • Storage 上传返回 res.data 解析失败:wx.uploadFile 的返回是字符串,需要解析;以及上传字段名必须是 file

与官方 SDK 的行为差异

supabase-wechat-stable-v2 虽然 API 对齐官方 SDK,但由于底层实现的限制,少数功能行为不同:

  • 无流式响应。 wx.request 不支持流式读取,只能等待完整响应返回。
  • wx.uploadFile 限制。 Storage 的文件上传底层走 wx.uploadFile,该 API 只支持 multipart/form-data,不支持直接上传二进制流(ArrayBuffer)。如果需要上传非文件类型的二进制数据,需要先写入本地临时文件再上传。
  • Realtime 受限。 因 phoenix 兼容性问题可能需要使用 stub,导致 Realtime 功能不可用。如需使用,参见前文的 phoenix 问题说明。