Wasp 服务端配置详解:通过 app.server 的 setupFn 与 middlewareConfigFn 定制 Express 启动流程与全局中间件
【免费下载链接】waspThe batteries-included full-stack framework for the AI era. Develop JS/TS web apps (React, Node.js, and Prisma) using declarative code that abstracts away complex full-stack features like auth, background jobs, RPC, email sending, end-to-end type safety, single-command deployment, and more.项目地址: https://gitcode.com/GitHub_Trending/wa/wasp
本文基于 Wasp 官方文档《Server Config》(web/versioned_docs/version-0.13/project/server-config.md)展开,系统讲解app声明中server字段的两个配置项——setupFn与middlewareConfigFn——的用法、执行时机与底层实现机制。读完后,你将能够在 Wasp 应用的 Node.js 服务启动阶段注册自定义路由、预初始化外部资源(数据库连接、定时任务等),并安全地修改作用于所有 Operations 与 API 的全局 Express 中间件栈,同时理解 Wasp 代码生成器是如何把你的配置函数"织入"到生成代码中的。
一、server 字段总览
你可以通过app声明的server字段来配置服务端的运行行为。完整的声明形式如下(JavaScript 与 TypeScript 两种 spec 语法写法一致):
// main.wasp app MyApp { title: "My app", // ... server: { setupFn: import { mySetupFunction } from "@src/myServerSetupCode.js", middlewareConfigFn: import { myMiddlewareConfigFn } from "@src/myServerSetupCode.js" } }server是一个字典,包含两个字段:setupFn: ExtImport与middlewareConfigFn: ExtImport。从源码结构看,这个字典在 Wasp 编译器(waspc)中对应一个独立的 Haskell 数据类型,定义于 Server.hs,其结构为:
data Server = Server { setupFn :: Maybe ExtImport, middlewareConfigFn :: Maybe ExtImport, envValidationSchema :: Maybe ExtImport }两个字段均为Maybe ExtImport,即都是可选的——你可以只配置其中之一,也可以都不配置。可以推断,该数据结构中的envValidationSchema字段是后续版本在server配置上新增的能力,0.13 版本文档中仅列出setupFn与middlewareConfigFn两项。
以下两节分别深入讲解这两个字段的实际用法。
二、setupFn:服务启动函数
setupFn声明一个 JavaScript/TypeScript 函数,该函数会在服务器启动时执行。它的典型用途包括:注册自定义 Express 路由、预初始化某些资源(额外的数据库连接、WebSocket、第三方客户端等)、启动 cron/定时任务。
官方文档给出的一个典型例子是添加一条自定义路由:
JavaScript 版:
// src/myServerSetupCode.js export const mySetupFunction = async ({ app }) => { addCustomRoute(app) } function addCustomRoute(app) { app.get('/customRoute', (_req, res) => { res.send('I am a custom route') }) }TypeScript 版:
// src/myServerSetupCode.ts import { ServerSetupFn } from 'wasp/server' import { Application } from 'express' export const mySetupFunction: ServerSetupFn = async ({ app }) => { addCustomRoute(app) } function addCustomRoute(app: Application) { app.get('/customRoute', (_req, res) => { res.send('I am a custom route') }) }2.1 函数上下文:express.Application 与 http.Server
setupFn的函数体会接收一个上下文对象,其中包含express.Application与http.Server两个实例,这对需要挂接自定义服务器逻辑的场景非常有用。TypeScript 中对应的公开类型定义来自 Wasp SDK 的wasp/server模块,其源码位于 types/index.ts:
// wasp/server export type ServerSetupFn = (context: ServerSetupFnContext) => Promise<void> export type ServerSetupFnContext = { app: Application // === express.Application server: Server // === http.Server }因此一个最简的 TypeScript 启动函数可以写成:
// src/myServerSetupCode.ts import { type ServerSetupFn } from 'wasp/server' export const mySetupFunction: ServerSetupFn = async () => { await setUpSomeResource() }2.2 执行时机:在开始接收请求之前 await
API 参考明确指出:setupFn被期望是一个 async 函数,并且 Wasp 会await 它完成之后才开始接受任何请求。这一点在生成器模板中可以找到直接证据。Wasp 编译时为每个应用生成服务端入口文件,其模板位于 server.ts,核心启动流程是:
const startServer = async () => { const port = normalizePort(config.port) app.set('port', port) const server = http.createServer(app) // 只有定义了 setupFn 时,下面这段代码才会被生成: const serverSetupFnContext: ServerSetupFnContext = { app, server } await ({= setupFn.importIdentifier =} as ServerSetupFn)(serverSetupFnContext) server.listen(port) // ...错误处理(EACCES / EADDRINUSE 等) }从这段模板可以看出三件事:
setupFn在http.createServer(app)之后、server.listen(port)之前被await调用,因此你在函数内完成的所有初始化(打开连接、启动定时任务等)都保证发生在服务器开始监听端口之前;- 上下文对象
{ app, server }正是ServerSetupFnContext类型的运行时实例——你在setupFn中拿到的app就是 Wasp 构造的 Express 实例,server是包装它的http.Server; - 模板中的
{=# setupFn.isDefined =}条件块表明,只有在main.wasp里真正声明了setupFn时,这段调用代码才会出现在生成产物中。
2.3 在 setupFn 中保存供后续使用的值(模块级单例模式)
如果你希望在 Operations(query/action)中使用某些在启动时创建的资源(例如某个重型客户端连接),推荐的模式是:在setupFn中初始化资源并保存在模块作用域,然后导出读取函数,供 Queries/Actions 直接导入使用。这样该模块就变成了一个"在服务启动时完成构造的单例"。
一个完整的示意例子(JavaScript 版):
// src/myServerSetupCode.js let someResource = undefined export const mySetupFunction = async () => { // 假设 setUpSomeResource 与 startSomeCronJob // 在下方实现或从别的文件导入。 someResource = await setUpSomeResource() startSomeCronJob() } export const getSomeResource = () => someResource// src/queries.js import { getSomeResource } from './myServerSetupCode.js' // ... export const someQuery = async (args, context) => { const someResource = getSomeResource() return queryDataFromSomeResource(args, someResource) }TypeScript 版结构相同,只是给函数补上类型标注(mySetupFunction: ServerSetupFn,Query 使用wasp/server/operations导出的SomeQuery类型):
// src/myServerSetupCode.ts import { type ServerSetupFn } from 'wasp/server' let someResource = undefined export const mySetupFunction: ServerSetupFn = async () => { someResource = await setUpSomeResource() startSomeCronJob() } export const getSomeResource = () => someResource// src/queries.ts import { type SomeQuery } from 'wasp/server/operations' import { getSomeResource } from './myServerSetupCode.js' // ... export const someQuery: SomeQuery<...> = async (args, context) => { const someResource = getSomeResource() return queryDataFromSomeResource(args, someResource) }官方文档的 note 对此模式给出了明确建议:把变量放在与 setup 函数相同的模块里,再暴露额外的读取函数,由 Operations 直接导入使用。Operations 本身的机制见 Operations 文档。
三、middlewareConfigFn:全局中间件配置函数
middlewareConfigFn声明一个 Express 中间件配置函数,用于全局性地修改所有 Operations(query/action)与 API 路由的中间件栈。例如为 CORS 增加允许来源、全局追加认证或日志中间件、调整 body 解析行为等。
3.1 Wasp 的默认全局中间件栈
从 Wasp 生成器的中间件模板 globalMiddleware.ts 可以看到,每个 Wasp 应用的 Express 服务器默认携带如下中间件(以Map维护,键名即身份):
// This is the set of middleware Wasp supplies by default. const defaultGlobalMiddlewareConfig: MiddlewareConfig = new Map([ ['helmet', helmet()], // 安全响应头 ['cors', cors({ origin: config.allowedCORSOrigins })], // CORS,允许来源来自应用配置 ['logger', logger('dev')], // morgan 请求日志 ['express.json', express.json()], // JSON body 解析(Operations 依赖它) ['express.urlencoded', express.urlencoded()], // urlencoded body 解析 ['cookieParser', cookieParser()] // 解析 req.cookies ])其中express.json对 Operations 的正常工作是必需的(服务端通过req.body反序列化入参,见 operations.ts 中deserialize(req.body)的用法),cors对前后端通信是必需的,这些细节也记载于 Configuring Middleware 文档。
3.2 用户配置函数的接入机制
模板中展示了middlewareConfigFn被接入的方式:
// 若用户声明了 middlewareConfigFn,则导入用户的函数; // 否则退化为恒等函数: const myMiddlewareConfigFn = (mc: MiddlewareConfig) => mc// 用户函数作用在默认中间件 Map 上,得到最终的全局中间件配置。 // 该配置是所有 Operations 与 API 路由的基础(除非它们再做各自的定制)。 const globalMiddlewareConfig: MiddlewareConfig = myMiddlewareConfigFn(defaultGlobalMiddlewareConfig)也就是说,middlewareConfigFn接收默认中间件Map作为参数,返回修改后的Map。模板中还暴露了globalMiddlewareConfigForExpress函数:它先克隆全局中间件 Map,再应用传入的每路由定制函数——克隆的目的是避免某个路由的定制污染其他路由看到的全局配置。这解释了为什么文档强调全局修改"影响所有 operations 和 APIs",并要求你对全局中间件的改动格外谨慎。
模板中// NOTE: Remember to update the docs of these change.这行注释也说明默认中间件清单与文档是成对维护的。完整的三级定制体系(global / per-api / per-path)见 Configuring Middleware 文档。
3.3 API 参考:app.server 字段一览
综合上文,app.server的字段定义如下(继承自原文档 API Reference):
// main.wasp app MyApp { title: "My app", // ... server: { setupFn: import { mySetupFunction } from "@src/myServerSetupCode.js", middlewareConfigFn: import { myMiddlewareConfigFn } from "@src/myServerSetupCode.js" } }setupFn: ExtImport—— 声明一个在服务启动时执行的 JavaScript/TypeScript 函数。函数必须是 async 的,且会在服务器开始接受任何请求之前被 await 完成。允许在其中做任何自定义初始化:例如建立额外的数据库/WebSocket 连接、启动 cron/定时任务。函数上下文接收express.Application与http.Server两个实例,便于挂接自定义服务器逻辑。类型签名为:export type ServerSetupFn = (context: ServerSetupFnContext) => Promise<void> export type ServerSetupFnContext = { app: Application // === express.Application server: Server // === http.Server }middlewareConfigFn: ExtImport—— 导入一个 Express 中间件配置函数的语句。这是一项全局性修改,影响所有 operations 与 APIs;详细用法见 Configuring Middleware 文档。
四、关键源码与文档索引
| 内容 | 路径 |
|---|---|
| 本文档(0.13 版 Server Config) | web/versioned_docs/version-0.13/project/server-config.md |
| Server 配置的数据模型(Haskell) | waspc/src/Wasp/AppSpec/App/Server.hs |
| 服务端启动入口模板(setupFn 的 await 时机) | waspc/data/Generator/templates/server/src/server.ts |
| 全局中间件模板(默认栈与配置函数接入) | waspc/data/Generator/templates/server/src/middleware/globalMiddleware.ts |
| Operations 请求处理模板(依赖 express.json) | waspc/data/Generator/templates/server/src/middleware/operations.ts |
ServerSetupFn/ServerSetupFnContext类型定义 | waspc/data/Generator/templates/sdk/wasp/server/types/index.ts |
| 中间件配置文档 | web/versioned_docs/version-0.13/advanced/middleware-config.md |
| Operations 概览文档 | web/versioned_docs/version-0.13/data-model/operations/overview.md |
五、小结
app.server是 Wasp 中定制 Node.js 服务端的唯一入口,当前包含setupFn与middlewareConfigFn两个可选的ExtImport字段;setupFn在server.listen之前被 await,接收{ app, server }上下文,适合注册自定义路由、初始化外部资源与定时任务;配合"模块级变量 + 读取函数"的模式,可在 Operations 中安全共享这些资源;middlewareConfigFn作用在默认中间件Map(helmet、cors、morgan、express.json、express.urlencoded、cookieParser)之上,其修改对所有 Operations 与 API 路由生效,因此应谨慎使用,路由级定制应使用 per-api / per-path 机制;- 以上结论均可在 waspc 编译器源码与生成器模板中找到对应实现,便于读者在生成产物中自行验证。
【免费下载链接】waspThe batteries-included full-stack framework for the AI era. Develop JS/TS web apps (React, Node.js, and Prisma) using declarative code that abstracts away complex full-stack features like auth, background jobs, RPC, email sending, end-to-end type safety, single-command deployment, and more.项目地址: https://gitcode.com/GitHub_Trending/wa/wasp
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考