Onlook 开源项目的 Supabase 后端栈:架构解析与本地运行实战指南
Onlook 开源项目的 Supabase 后端栈架构解析与本地运行实战指南【免费下载链接】onlookThe Cursor for Designers • An Open-Source AI-First Design tool • Visually build, style, and edit your React App with AI项目地址: https://gitcode.com/GitHub_Trending/on/onlook导读Onlook 是一款开源的 AI-First 可视化设计工具用于以可视化方式构建、样式化并编辑 React 应用。其在线能力用户管理、多端协作、数据持久化等并非自建服务而是构建在Supabase之上——一个集 PostgreSQL 数据库、认证、实时订阅与对象存储于一体的开源 Backend-as-a-Service 平台。本文以 apps/backend/README.md 为骨架结合仓库中的config.toml、20 个数据库迁移脚本与 Drizzle ORM 数据层源码完整解析 Onlook 后端栈的设计动机、本地启动流程、数据库结构与安全策略帮助你掌握如何在本地或自托管环境中完整复现这套后端。为什么 Onlook 需要一个后端栈Onlook 的核心编辑体验发生在浏览器与本地文件系统中但要让产品具备真正的在线能力必须有一层服务端基础设施。README 中明确了这层后端栈的定位用户管理注册、登录、个人资料与订阅状态协作项目、画布canvas、帧frame的多用户共享与权限控制数据持久化项目元数据、对话记录、消息历史、使用量统计等跨设备持久保存。Onlook 选择直接构建在 Supabase 之上而不是自研 API 服务器因为它同时满足两个需求既能以云端托管实例形式运行也能完全在本地运行或自托管。README 同时给出了产品的设计取向——Ideally, the product should still work offline with no backend connection即产品在无后端连接时应保持可用编辑能力本地化后端只负责在线增强未来官方也会提供托管实例。从仓库结构看整个后端被组织为三个相互配合的部分部分位置职责Supabase 配置与迁移apps/backend/supabaseconfig.toml定义本地/自托管实例参数migrations/存放数据库 schema 与 RLS 策略数据访问层packages/db基于 Drizzle ORM 的类型安全客户端、schema 定义、默认值与种子数据领域模型packages/models前后端共享的 TypeScript 类型项目、角色、订阅等本地运行四步启动完整后端在开始前需要准备 Docker 环境因为 Supabase 本地实例以容器方式运行以下步骤来自 apps/backend/README.md。第 1 步安装依赖bun install项目使用 Bun 作为包管理器根目录存在 bun.lock 与 bunfig.tomlapps/backend/package.json的 devDependencies 中声明了supabase当前版本^2.45.5CLI 工具。第 2 步启动本地 Supabase 实例bun run start该命令对应package.json中的start: supabase start会拉取并启动一组 Docker 容器PostgreSQL、GoTrue 认证、Realtime、Storage、Studio 等。启动完成后可以通过bun run status即supabase status -o json查看各服务的连接信息与端口。第 3 步重置数据库到最新快照bun run reset对应reset: supabase db reset会按照 apps/backend/supabase/migrations 目录下按序编号的 20 个 SQL 迁移文件0000_same_human_robot.sql至0019_abandoned_psylocke.sql附带_journal.json与各版本的meta/*.json快照重建数据库 schema。supabase db reset会丢弃本地数据库并按迁移历史重建使本地环境与最新 schema 保持一致。其他常用命令定义于 apps/backend/package.json命令底层 CLI用途bun run stopsupabase stop停止本地实例bun run pushsupabase db push将 schema 变更推送到远程数据库用于指向云上或自托管库bun run db:gensupabase gen types --langtypescript --local --schema public从本地库生成 TypeScript 类型输出到packages/supabase/src/types/db.tsbun run testcd supabase/functions/api deno test运行 Supabase Edge Function 的 Deno 测试bun run statussupabase status -o json以 JSON 输出实例状态本地配置全景config.toml 关键参数本地与自托管实例的行为由 apps/backend/supabase/config.toml 控制。以下是核心配置项的含义与取值project_id onlook-web [api] enabled true port 54321 schemas [public, storage] extra_search_path [public] max_rows 100 [auth] site_url https://onlook.com additional_redirect_urls [ http://localhost:3000, http://localhost:3000/auth/callback, ] jwt_expiry 36000 [db] port 54322 [studio] port 54323project_id项目标识本地部署时用于区分不同实例[api] port 54321REST/PostgREST API 端口schemas暴露public与storage两个 schemamax_rows 100限制单次查询最多返回 100 行防止客户端拉取全表[auth]site_url指向线上站点additional_redirect_urls加入本地开发地址localhost:3000及认证回调路径使本地 Web 应用可以完成 OAuth 回调jwt_expiry 36000秒即 10 小时控制登录令牌有效期[db] port 54322PostgreSQL 直连端口Drizzle 客户端与supabase gen types均通过该端口访问数据库[studio] port 54323Supabase Studio 管理界面端口可在浏览器中查看表数据、执行 SQL。第三方登录与 Edge Function[auth.external.github] enabled true client_id env(GITHUB_CLIENT_ID) secret env(GITHUB_SECRET) redirect_uri http://127.0.0.1:54321/auth/v1/callback [auth.external.google] enabled true client_id env(GOOGLE_CLIENT_ID) secret env(GOOGLE_SECRET) redirect_uri http://127.0.0.1:54321/auth/v1/callback [functions.stripe-webhook] verify_jwt false [storage.buckets.preview_images] public true file_size_limit 10MiBGitHub 与 Google 登录均开启凭据通过环境变量注入env(...)语法本地回跳地址为 Supabase 认证回调端口stripe-webhookEdge Function 关闭 JWT 校验verify_jwt false因为 Stripe 的 webhook 请求使用 Stripe 签名而非 Supabase JWT 验证preview_images存储桶为公开public true单个文件上限 10MiB用于存放项目预览图。存储桶与文件权限仓库中的迁移脚本完整定义了存储层策略与config.toml的存储桶配置相互印证0008_preview-img-storage.sql 创建公开的preview_images桶并放行storage.objects上的公开 SELECT 与 INSERT 策略0012_file-transfer-bucket.sql 创建私有的file_transfer桶其 SELECT/INSERT/DELETE 策略均要求auth.uid() owner即用户只能访问属于自己的传输文件0009_project_img_path.sql 为projects表维护preview_img_url、preview_img_path、preview_img_bucket三个字段把项目预览图与其所在桶、路径关联起来。数据库结构从迁移历史看核心表设计Onlook 的数据库 schema 完全由 Drizzle ORM 定义见 packages/db/drizzle.config.ts其out指向apps/backend/supabase/migrations再经supabase db reset应用。首个迁移 0000_same_human_robot.sql 奠定了核心表结构users以auth.users的外键关联 Supabase 认证用户id引用auth.users(id)业务侧用户表是认证系统的延伸projects项目主表记录名称、sandbox 信息、预览图字段与描述canvas画布设计工作区通过project_id级联关联项目记录缩放与坐标frames画布内的帧预览窗口记录类型frame_type枚举当前为web、URL、位置与尺寸并通过canvas_id级联关联conversations / messagesAI 对话与会话消息消息支持roleuser/assistant、applied是否已应用、snapshots、context消息上下文与parts等 JSONB 字段user_projects用户与项目的多对多关系表复合主键user_id project_id是权限判断的核心依据。后续迁移持续演进功能例如 0016_pretty_dust.sql 引入rate_limits表按订阅周期追踪消息用量含max/left/carry_over等字段与usage_records并为messages、conversations补充checkpoints、suggestions等字段同时为users增加stripe_customer_id以对接计费。订阅体系则由subscriptions、products、prices等表支撑。安全模型RLS 策略与协作权限Onlook 的协作权限完全依赖 PostgreSQL 的**行级安全RLS**实现所有核心表均显式ENABLE ROW LEVEL SECURITY。关键实现在 0006_rls.sql两个可复用的权限判断函数-- 判断当前用户在某项目上是否拥有指定角色 CREATE OR REPLACE FUNCTION user_has_project_access( project_id_param UUID, required_roles TEXT[] ) RETURNS BOOLEAN AS $$ BEGIN RETURN EXISTS ( SELECT 1 FROM user_projects WHERE user_projects.project_id project_id_param AND user_projects.user_id auth.uid() AND user_projects.role ANY(required_roles) ); END; $$ LANGUAGE plpgsql SECURITY DEFINER; -- 通过画布反查项目权限 CREATE OR REPLACE FUNCTION user_has_canvas_access( canvas_id_param UUID, required_roles TEXT[] ) RETURNS BOOLEAN AS $$ BEGIN RETURN EXISTS ( SELECT 1 FROM canvas c JOIN user_projects up ON c.project_id up.project_id WHERE c.id canvas_id_param AND up.user_id auth.uid() AND up.role ANY(required_roles) ); END; $$ LANGUAGE plpgsql SECURITY DEFINER;两个函数均以SECURITY DEFINER执行绕过调用者的表权限限制只按函数内逻辑判断是后续所有策略的公共基础。以projects表为例的策略矩阵操作允许对象依据INSERT任意已认证用户WITH CHECK (true)SELECT项目的 owner / adminuser_has_project_access(id, ARRAY[owner,admin])UPDATE项目的 owner / admin同上DELETE仅 owneruser_has_project_access(id, ARRAY[owner])同样的角色分级模式贯穿canvas、frames、user_projects、project_invitations等表owner 拥有删除权限owner/admin 可读写普通协作者只能读取。users、user_settings、user_canvases则采用仅本人可操作策略auth.uid() users.id。project_invitations允许邀请人、项目 owner/admin 以及被邀请人按邮箱匹配auth.users查看与处理邀请构成了完整的项目协作闭环。实时协作Realtime 广播触发器协作体验还依赖 Supabase Realtime。0007_realtime_rls.sql 定义了一个project_changes()触发器函数当conversations或messages发生 INSERT/UPDATE/DELETE 时通过表间关联解析出所属project_id并调用realtime.broadcast_changes将变更广播到topic:project_id主题——订阅该主题的前端即可实时收到对话与消息更新同时为realtime.messages放开已认证用户的 SELECT 策略允许客户端接收广播。这种按项目维度广播的设计把实时流量精确限定在参与该项目的用户范围内。数据访问层与种子数据Drizzle 客户端后端访问数据库统一通过 packages/db/src/client.ts使用postgres-js建立连接连接串取自SUPABASE_DATABASE_URL环境变量对应本地postgresql://postgres:postgres127.0.0.1:54322/postgres见 drizzle.config.ts再由 Drizzle 封装为类型安全客户端并注册全部 schema。开发环境下连接被缓存在globalThis避免热更新HMR时重复建连。种子脚本 packages/db/src/seed/db.ts 在一个数据库事务内写入一名种子用户Joan Doe邮箱supportonlook.com定义于 seed/constants.ts、两个测试项目、四个分支、画布、帧、对话与消息、以及完整的 Stripe 产品/价格/订阅/限流记录便于本地联调协作与 AI 对话功能。其中分支与项目的关系、ProjectRole.OWNER的成员关系与 RLS 函数中的角色体系完全对应。局限与注意事项本地启动前置条件bun run start依赖 Docker且首次启动会拉取镜像耗时较长bun run reset会丢弃本地数据库请勿在含重要数据的库上执行类型生成输出路径bun run db:gen将类型输出到packages/supabase/src/types/db.ts若该包路径在后续版本中调整需同步修改package.json脚本OAuth 凭据GitHub/Google 登录需先在各自开发者控制台创建应用并将回调地址配置为http://127.0.0.1:54321/auth/v1/callback再以环境变量提供GITHUB_CLIENT_ID/GITHUB_SECRET/GOOGLE_CLIENT_ID/GOOGLE_SECRET生产部署README 明确官方托管实例尚未开放线上部署需自行托管 Supabase 或使用其他 Supabase 服务并保持离线可用的设计目标——核心编辑功能不依赖后端连接。总结Onlook 的后端栈展示了一条清晰的实践路径以 Supabase 作为托管或自托管的 BaaS 底座用 Drizzle ORM 定义 schema 并通过 20 个迁移脚本沉淀数据库演进用 RLS 策略与SECURITY DEFINER函数实现项目级协作权限用 Realtime 广播支撑协作体验再用类型安全的数据访问层与种子数据串联起本地开发闭环。从 apps/backend/README.md 出发配合 supabase/config.toml、迁移目录 与 packages/db 源码你可以完整理解并独立复现这套后端甚至将其作为自托管 Supabase 项目的参考样板。【免费下载链接】onlookThe Cursor for Designers • An Open-Source AI-First Design tool • Visually build, style, and edit your React App with AI项目地址: https://gitcode.com/GitHub_Trending/on/onlook创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考