VibeTrip 是我在做的旅行规划应用,面向慕尼黑的用户。有一个时间点我需要做一个技术决策:Express 后端要迁移到哪里?
这不是一个"哪个框架更好用"的问题,因为有一些硬约束在那里。
我当时的情况是:Supabase Cloud 的免费配额用完了——被 Manitodo 占着。自托管的 Supabase 跑在国内的服务器上,这对面向德国用户的 VibeTrip 来说不可能用,延迟和 GDPR 都过不了。Cloudflare 账户里有 D1、KV、Workers,都还没动过,配额完整。
所以问题不是"全局最优解是什么",而是"在这个约束下,哪个选择说得通"。
第一个被排除掉的是 Prisma。
不是因为 Prisma 不好用,是它的架构和 Cloudflare Workers 的运行时根本不兼容。
Prisma 依赖一个叫 Query Engine 的东西——一个用 Rust 编译的二进制文件,在应用启动时作为子进程运行,所有数据库操作都通过它转发。这意味着 Prisma 需要一个持久进程。
Workers 是边缘运行时,每个请求独立执行,没有持久进程的概念。Worker 启动、处理请求、结束。下一个请求来了再启动一个新的。Prisma 的 Query Engine 在这里根本没有地方活。
这不是配置问题,是架构问题。Prisma 在这里不是"不推荐",是根本跑不起来。
所以用了 Drizzle。Drizzle 直接生成 SQL,没有中间层,完全兼容边缘环境。
认证方案原来是 Passport.js + Google OAuth。
Passport.js 依赖 Node.js 的 req/res 对象和 session 中间件。Workers 的运行时是基于 Web Standard API 的,不是完整的 Node.js 环境。"为 Workers 适配 Passport.js"的实际意思是:自己手动实现 OAuth 2.0 PKCE 流程,只是把 Passport.js 的名字保留在那里。
那不如直接换掉。Auth.js v5 重写了架构,原生支持 Edge Runtime,Google OAuth 直接配置。
还有一个顾虑:VibeTrip 需要调用慕尼黑的公共交通 API(MVG/MVV),如果这些 API 有 IP 地域限制,Workers 从哪里出去就很重要。
调研之后发现这个问题基本不存在。Workers 默认在离用户最近的数据中心运行,用户在慕尼黑,请求自然从法兰克福节点出去,出口 IP 是欧洲 IP。
但有一个例外:Cron Job。定时任务不是用户触发的,Cloudflare 不保证它从欧洲节点运行。当前 MVP 没有定时任务,这个风险先放着。
从 Express 迁到 Workers,没有选择重写,选择了并行。
Express 保持原样放在一个目录里,Workers 在另一个目录里从零开始搭,逐条路由验证行为一致,验证完一条删一条对应的 Express 路由,最后 Express 整个目录删掉。任何时刻都可以回滚,不会有迁移到一半跑不通的状态。
每次技术选型,最值得花时间搞清楚的不是"哪个工具更好",而是"哪些工具在这个环境里根本用不了"。把不可能的选项排除掉之后,剩下的往往没那么难选。