- 后端
- 微服务
- 云原生
【免费下载链接】midway
🍔 A Node.js Serverless Framework for front-end/full-stack developers. Build the application for next decade. Works on AWS, Alibaba Cloud, Tencent Cloud and traditional VM/Container. Super easy integrate with React and Vue. 🌈
导读
本文基于 Midway 官方指南 前端集成,给出一条可直接落地的路线,讲解如何把已有的 React/Vue 前端项目与函数式 Midway(defineApi定义的 API)无缝接起来:前端通过类型安全的createClient客户端直接调用服务端 API 定义,Vite 开发服务器同时托管 Midway 业务接口,@midwayjs/web-bridge的构建插件负责把"服务端 API 定义"翻译成浏览器可安全加载的"路由契约"。读完本文你将掌握:依赖安装与目录组织、defineApi+ zod 的服务端 API 定义、前端 client 的创建与调用、Vite/Rspack 桥接配置、函数式中间件的路由级与模块级挂载方式,以及自定义目录时的注意事项。
1. 安装依赖:在前端项目中补齐 Midway 依赖
如果你已经是一个前端项目(有package.json、Vite、React/Vue),不需要重建工程,只需补齐 Midway 相关依赖。核心包分为四类职责:
@midwayjs/core:函数式 Midway 的运行时核心,提供defineApi等函数式 API 能力;@midwayjs/web-bridge:前后端桥接层,提供 Vite/Rspack 构建插件与底层的createClient实现;@midwayjs/mock:开发期把 Midway 应用"挂"到 Vite 上的devPlugin;@midwayjs/react/@midwayjs/vue:对应框架的createClient导出(内部复用@midwayjs/web-bridge);zod:函数式 API 的运行时 schema 校验与类型推导。
React 项目:
$ npm i @midwayjs/core @midwayjs/web-bridge @midwayjs/mock @midwayjs/react zodVue 项目:
$ npm i @midwayjs/core @midwayjs/web-bridge @midwayjs/mock @midwayjs/vue zod2. 准备目录:一个项目内同时容纳前端与函数式服务端
推荐的项目结构如下,前端与函数式服务端共存于同一个src下,通过src/web(浏览器代码)与src/server(服务端 API 定义)做物理隔离:
. ├── package.json # 项目脚本与依赖 ├── vite.config.ts # Vite 配置(含 devPlugin/apiPlugin) ├── src │ ├── main.tsx / main.ts # React/Vue 入口 │ ├── web │ │ ├── app.tsx / app.vue # 前端根组件 │ │ └── api │ │ └── client.ts # 前端 API 客户端 │ └── server │ ├── index.ts # Midway 服务端入口(也可命名为 configuration.ts) │ └── api │ └── user.api.ts # 服务端 API 定义 └── tsconfig.json # TypeScript 配置仓库中的 react-functional-api 示例 正是按此布局组织:src/server/api/user.api.ts定义服务端 API,src/web/api/client.ts创建前端客户端,src/main.tsx作为 React 入口。注意src/web与src/server是推荐约定而非强制,详见文末第 10 节"自定义目录说明"。
3. 定义服务端 API:defineApi+ zod 的输入输出契约
在src/server/api/user.api.ts中,用函数式 API 语法定义路由、输入校验与输出类型:
// src/server/api/user.api.ts import { defineApi } from '@midwayjs/core/functional'; import { z } from 'zod'; export const userApi = defineApi('/users', api => ({ getUser: api .get('/:id') .input({ params: z.object({ id: z.string() }), }) .output( z.object({ id: z.string(), name: z.string(), }) ) .handle(async ({ input }) => { return { id: input.params.id, name: 'harry', }; }), }));这里有两点值得展开说明(原文明确强调了这两个"效果"):
input(...)是运行时校验器:它不只声明类型,还会在请求真正到达handle之前,对params(路径参数)、query(查询参数)、body(请求体)、headers(请求头)执行 zod schema 校验,非法请求会被拦截,不会进入业务处理函数;- schema 类型直接流向
handle(...):zod 的z.infer类型会沿调用链传递,因此handle的参数input自带类型信息,可以直接写出input.params.id,无需手写一遍 TS 接口,前后端契约单一来源(single source of truth)。
仓库示例 user.api.ts 还展示了带meta的写法,通过.meta({ routerName: 'getUser' })给路由一个稳定的操作名,该名称会作为operationId的一部分被 client 使用。
4. 创建前端 client:把服务端 API 定义变成可调用的客户端对象
前端只需要从@midwayjs/react(或@midwayjs/vue)导入createClient,把服务端 API 定义按命名空间传入,即可得到类型安全的调用对象。
React:
// src/web/api/client.ts import { createClient } from '@midwayjs/react'; import { userApi } from '../../server/api/user.api'; export const api = createClient( { user: userApi }, { basePath: '/api' } );Vue:
// src/web/api/client.ts import { createClient } from '@midwayjs/vue'; import { userApi } from '../../server/api/user.api'; export const api = createClient( { user: userApi }, { basePath: '/api' } );从源码看,这两个包的createClient都是对 @midwayjs/web-bridge 的 createClient 的再导出:React 侧见 bridge.ts,Vue 侧见 index.ts。底层createClient会遍历每个模块的路由,拼出operationId(形如user.getUser)、method与fullPath,最终生成形如api.user.getUser(input)的调用函数,并附带call(operationId, input)、has(operationId)、operationIds()三个通用方法。
关于basePath的进阶用法:basePath除了字符串,还支持对象或函数(见 CreateClientOptions)。在 SSR / 同构场景下,浏览器与 Node 端需要不同的基地址——浏览器访问/api(同源相对路径),服务端渲染时访问http://127.0.0.1:7001/api(绝对地址)。示例项目 client.ts 正是这样配置的:
export const api = createClient( { user: userApi }, { basePath: { browser: '/api', server: 'http://127.0.0.1:7001/api', }, } );resolveRuntimeBasePath(见 api-bridge 源码)会依据运行环境(是否检测到window.document)自动选择browser/server分支。默认的 HTTP transport 使用全局fetch作为底层 adapter,路径参数通过encodeURIComponent安全替换、query 用URLSearchParams序列化;如果项目已接入 axios,也可以使用createAxiosAdapter(axiosInstance)替换默认 fetch 实现(createAxiosAdapter)。
5. 在页面里调用:像调用本地函数一样调用远端 API
React 页面组件:
import { useEffect, useState } from 'react'; import { api } from './api/client'; export function UserPage() { const [name, setName] = useState(''); useEffect(() => { api.user.getUser({ params: { id: 'u-1' } }).then(user => { setName(user.name); }); }, []); return <div>{name}</div>; }Vue 的setup():
// setup() const user = await api.user.getUser({ params: { id: 'u-1' }, });调用约定非常直观:输入对象固定为{ params, query, body, headers }四个可选字段,分别对应路径参数、查询串、请求体与请求头(HttpClientInputShape),与第 3 节input(...)声明的校验维度一一对应。api.user.getUser的返回值类型由服务端.output(z.object({...}))推导而来,因此user.name无需任何手动类型标注。
Vue 生态还额外提供了依赖注入式的用法:MidwayApiProvider组件或createMidwayApiPlugin(client)插件向应用注入 client,组件内通过useMidwayApiClient()/useMidwayApiOperation(operationId)取用(详见 Vue 封装源码),适合需要把 client 与组件解耦的场景。
6. 配置 Vite 桥接:devPlugin + apiPlugin 双插件
这是"前后端接起来"的关键一步。在vite.config.ts中同时启用两个插件:
import { defineConfig } from 'vite'; import { devPlugin } from '@midwayjs/mock/vite'; import { apiPlugin } from '@midwayjs/web-bridge/vite'; export default defineConfig({ plugins: [ devPlugin({ appDir: process.cwd(), baseDir: 'src/server', basePath: '/api', }), apiPlugin({ root: process.cwd(), apiDir: 'src/server/api', target: 'both', }), ], });两个插件职责分明:
devPlugin(来自 @midwayjs/mock 的 vite 插件):把 Midway 函数式应用作为请求处理器挂到 Vite 开发服务器上。appDir指向项目根目录,baseDir指向服务端代码目录(src/server),basePath指定接口前缀(/api),浏览器发往/api/*的请求由此进入 Midway 处理;apiPlugin(来自 @midwayjs/web-bridge 的 vite 插件):apiDir指向服务端 API 定义目录,target决定转换作用于哪个构建目标:'client'(纯浏览器构建)、'ssr'(仅服务端渲染构建)或'both'(两者都生效)。
apiPlugin的底层原理值得了解:它以\0midway-api:前缀创建虚拟模块,在resolveId阶段拦截对apiDir内、且包含defineApi的文件的导入,用 transformDefineApiSource 做一次轻量源码扫描——只提取defineApi的prefix、各路由的method/path与meta信息,生成纯浏览器安全的"路由契约"代码(toCode),服务端的handle业务逻辑与 zod 校验器不会被打进前端包。插件还会通过addWatchFile与handleHotUpdate支持 API 定义文件的热更新。
React 项目记得再加@vitejs/plugin-react,Vue 项目再加@vitejs/plugin-vue。完整的真实配置可对照示例项目 vite.config.ts——其中basePath直接复用了 client 里声明的apiBridgeConfig.browserBasePath,避免两处硬编码不一致。
7. 函数式中间件:路由级与模块级两种挂载方式
函数式 API 的中间件支持两种作用域:
路由级——只对单个路由生效,通过.meta({ middleware: [...] })挂载:
api.get('/:id').meta({ middleware: [authMw] }).handle(async () => ({}));模块级——对整个 API 模块下的所有路由生效,通过defineApi的第三个参数挂载:
defineApi('/users', api => ({ getUser: api.get('/:id').handle(async () => ({})), }), { middleware: [authMw], });两种方式都可叠加多个中间件,典型的场景如鉴权(authMw)、日志、限流等横切逻辑,写在函数式 API 层可以复用到所有前端调用方。
8. 启动与验证
- 运行
$ npm run dev启动开发服务器; - 打开页面触发一次 API 调用(例如进入
UserPage); - 在浏览器开发者工具的网络面板中确认请求命中
/api/*(如GET /api/users/u-1)。
验证通过的标准:请求确实进入了 Midway 函数式 API 处理、响应结构与output(...)声明的 schema 一致、前端user.name渲染出服务端返回的'harry'。
9. Rspack 场景(可选)
如果构建工具不是 Vite 而是 Rspack,@midwayjs/web-bridge提供对应的 loader 与 rule 工厂。在 Rspack 配置中直接使用:
createApiRspackRule({ root: process.cwd(), apiDir: 'src/server/api', });该工厂返回一条enforce: 'pre'的 rule(createApiRspackRule):test匹配\.[cm]?[jt]sx?$文件,include限定在apiDir内,use指向@midwayjs/web-bridge/rspackloader。loader 内部(apiRspackLoader)复用与 Vite 插件同一套toWebSafeApiContractCode转换逻辑,将defineApi源码改写为浏览器安全的路由契约代码——因此 Vite 与 Rspack 两套链路得到的契约语义完全一致。
10. 自定义目录说明
src/web/api与src/server/api只是推荐目录,不是强制约定。你可以改成src/client/api、src/apis等其他任意位置,只要保证以下两处同步即可:
- 前端
client.ts的真实路径与导入路径:client.ts中import { userApi } from '../../server/api/user.api'的相对路径,必须与实际存放服务端 API 定义的位置一致; - 构建插件
apiDir指向正确的服务端 API 定义目录:vite.config.ts/ Rspack 配置里的apiDir必须指向同一个目录,否则apiPlugin/createApiRspackRule无法扫描到defineApi,前端也就拿不到对应的路由契约。
小结:一条可复制的全栈落地链路
回顾整条链路:defineApi用 zod 声明输入输出(服务端契约唯一来源)→@midwayjs/react|vue的createClient把契约变成类型安全的调用对象 →devPlugin让 Vite 直接处理/api/*请求 →apiPlugin/createApiRspackRule把服务端 API 定义转换为浏览器安全的路由契约,并随文件热更新。整条链路的核心实现分别位于 api-bridge 客户端内核、web-bridge 构建插件 与 Rspack loader,配合 react-functional-api 完整示例 可直接对照落地。
- 后端
- 微服务
- 云原生
【免费下载链接】midway
🍔 A Node.js Serverless Framework for front-end/full-stack developers. Build the application for next decade. Works on AWS, Alibaba Cloud, Tencent Cloud and traditional VM/Container. Super easy integrate with React and Vue. 🌈
相关推荐
Midway 函数式 API 与 Vue 前端集成指南:从 defineApi 到 createClient 的端到端打通
Midway 函数式 API 与 Vue 前端集成指南:从 defineApi 到 createClient 的端到端打通 本篇技术指南以 site/docs/
后端微服务云原生Midway Functional Web Routing API 设计指南:defineApi 链式 DSL、纯函数式服务与 React/Vue 前后端一体化开发
Midway Functional Web Routing API 设计指南:defineApi 链式 DSL、纯函数式服务与 React/Vue 前后端一体化
后端微服务云原生在 Vue 3 中调用 Midway 函数式 API:@midwayjs/vue 桥接插件与组合式 API 实践指南
在 Vue 3 中调用 Midway 函数式 API:@midwayjs/vue 桥接插件与组合式 API 实践指南 导读 @midwayjs/vue 是 Mi
后端微服务云原生
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考