规范使用 TanStack Start Server Functions
摘要
在 TanStack Start 中,Server Functions 极容易成为代码堆砌的灾难区。这篇文章探讨了如何将其牢牢按接在口层的位置:专注鉴权、限流、参数校验与中间件编排,而把真正的业务逻辑还给 Service。
接着上篇聊路由层面防线的话题,咱们继续顺着 TanStack Start 的请求链路往深处走:这次聊聊 Server Functions。
当项目脱离了“Demo 期”,真正开始承载复杂业务逻辑时,首当其冲要面临考验的就是这一层。
刚上手时,我们很容易顺手就把数据库查询、数据拼装甚至权限判断全都糊在一个 createServerFn 里。起初这会让你觉得全栈开发“效率奇高”,但很快,你就会在代码库里迷失。
在这个博客项目里,我对 createServerFn 的定位极其克制:它是纯粹的接口层,绝不是业务层。
它只负责这几件苦力活:
声明 HTTP Method
编排中间件拦截器
校验入参格式
把合法请求恭敬地递交给后方的 Service
至于真正的业务细节?它一行都不应该有。
接口层解剖:评论创建逻辑
评论创建功能,是最能体现 Server Functions 价值的场景。
你看,仅仅这十几行代码,其实已经把一个生产级接口要求的东西拉满了:
这是写操作,所以锁定了
POST串联了限流兜底(基于 Durable Objects 分布式限流)
串联了 Turnstile 人机验证
串联了必须登录的 Auth 校验
inputValidator对抗所有的脏输入最终,把干净的
data丢给CommentService
整个结构非常漂亮。接口层有明确的宏观职责,但它自己绝对不会长出不可控的业务逻辑分支。
中间件编排:告别面条代码
如果不利用中间件,我们在传统的 API Controller 里得怎么写?
恐怕得一遍遍地手写:先连数据库,再验 Session,再查验证码,再算限流额度……
相比之下,Server Functions 利用中间件组合的写法简直是降维打击。比如,普通的查询接口和刚才的写接口,权限要求就截然不同:
读接口只需要挂载个轻量的 sessionMiddleware 就够了。一旦底层中间件体系搭建完毕,新写一个接口就如同搭积木一样简单。
强类型的文件上传:FormData处理
全栈框架处理起 JSON 往往很丝滑,但一碰到文件上传等 FormData 场景,接口层就开始群魔乱舞。
在 TanStack Start 里,处理文件导入其实和处理 JSON 没什么心智负担:
依然保持着强类型约束:
请求方法明确
权限隔离明确
入参对象收束明确
这里顺带提一嘴,不管是在处理表单还是普通校验阶段,遇到异常一定要抛出类型安全的 Error,前两天总结的那篇 用 Result 类型处理全栈错误 同样适用于 Server Functions 的 handler 返回层。
Service 纯粹化
标签系统和后台索引重建的接口,最能展现这种“强行压薄”的爽感:
没有废话。
如果你用传统的框架去写 API 路由文件(比如 Next.js 的 App Router Route Handlers 或者原始的 Express),巨大的空白区域很容易诱惑你往里面塞几行“临时”的 SQL 查询或者胶水逻辑。
而 createServerFn 这种链式调用的写法,直接在视觉和语法上锁死了你乱写代码的空间。它会“逼着”你老老实实地回 Service 模块里去设计聚合根和领域方法。
Server Functions 的边界
我不会把 Server Functions 吹成无所不能的银弹。
它的主场在同构全栈体系内:它让前后端的互相调用几乎没有摩擦,类型直接打通,省去了手写 Swagger 和 Fetch 的冗长痛点。
但它也有无法覆盖的盲区:
如果你需要开放正规的 RESTful / GraphQL API 给第三方客户端接入,光靠 Server Functions 肯定不踏实,还是得外挂一层真正的外部 API 层。
对于脱离了框架 HTTP 请求生命周期的任务,比如 Cloudflare Workflows、Queues 的消费者,这套逻辑自然也管不着。
这也是为什么我的博客架构里,除了这套对内的 Server Functions,针对特殊场景还分别暴露了纯粹的 Hono 接口、MCP Entrypoint 和 Workflow 的触发器。
没有任何一种工具有资格包揽全部,看菜下饭就好。
总结
一句话:永远把 Server Functions 当接线员,不要当包工头。
它存在的意义是把网上的“荒蛮报文”,优雅地洗出:
靠谱的 HTTP Method
层层清洗过的 Middleware Context
格式极其确定的 Input Validator
然后呢?然后就交给后端 Service 干活去吧。
