news 2026/10/9 3:16:18

Midway 函数式开发前端集成:React/Vue 项目与函数式服务端 API 的零断点桥接实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Midway 函数式开发前端集成:React/Vue 项目与函数式服务端 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. 🌈

项目地址:https://gitcode.com/gh_mirrors/mi/midway
点击查看免费下载

导读

本文基于 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 zod

Vue 项目:

$ npm i @midwayjs/core @midwayjs/web-bridge @midwayjs/mock @midwayjs/vue zod

2. 准备目录:一个项目内同时容纳前端与函数式服务端

推荐的项目结构如下,前端与函数式服务端共存于同一个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', }; }), }));

这里有两点值得展开说明(原文明确强调了这两个"效果"):

  1. input(...)是运行时校验器:它不只声明类型,还会在请求真正到达handle之前,对params(路径参数)、query(查询参数)、body(请求体)、headers(请求头)执行 zod schema 校验,非法请求会被拦截,不会进入业务处理函数;
  2. 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. 启动与验证

  1. 运行$ npm run dev启动开发服务器;
  2. 打开页面触发一次 API 调用(例如进入UserPage);
  3. 在浏览器开发者工具的网络面板中确认请求命中/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等其他任意位置,只要保证以下两处同步即可:

  1. 前端client.ts的真实路径与导入路径:client.ts中import { userApi } from '../../server/api/user.api'的相对路径,必须与实际存放服务端 API 定义的位置一致;
  2. 构建插件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. 🌈

项目地址:https://gitcode.com/gh_mirrors/mi/midway
点击查看免费下载
上一篇:终极指南:如何快速掌握ModSecurity v3 Web应用防火墙
下一篇:【亲测免费】 Stable Diffusion 开源项目教程

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/9 3:16:03

OpenClaw Skill机制详解与精选清单:从入门到全平台部署实操

最近一直在折腾OpenClaw&#xff0c;从一个只会拿来聊天的普通用户&#xff0c;到慢慢把各种skill玩出花来&#xff0c;这个过程踩了不少坑&#xff0c;也攒了不少心得。OpenClaw这套东西&#xff0c;说白了就是一个开源的AI助手平台&#xff0c;核心思路是把“大模型对话”变成…

作者头像 李华
网站建设 2026/10/9 3:16:01

校园二手交易平台Java开发实战:轻量级生产系统搭建指南

简介&#xff1a;本资源是一个基于Java技术栈开发的校园二手交易平台完整项目源码包&#xff0c;面向计算机专业本科生、Java初学者及Web应用开发学习者&#xff0c;旨在解决高校学生间教材、数码产品、生活用品等闲置物品高效流转的实际需求。压缩包共440个文件&#xff0c;体…

作者头像 李华
网站建设 2026/10/9 3:15:57

Geek Uninstaller实战:彻底卸载Windows残留的轻量工具

Windows自带的“卸载程序”有多不靠谱&#xff0c;但凡在电脑前坐过几年的人都深有体会。装个软件三天后想去掉&#xff0c;先在控制面板里翻半天找卸载入口&#xff0c;点完“下一步”发现桌面快捷方式还赖着不走&#xff0c;右键菜单里那些残留项更是一堆&#xff0c;注册表里…

作者头像 李华
网站建设 2026/10/9 3:15:57

嵌入式Android屏幕点亮:Panel驱动移植实战指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/9 3:15:42

Git从入门到实战:常见问题排查与团队协作规范

1. 环境准备与初始配置 1.1 安装 Git&#xff1a;Windows、macOS、Linux 三平台实操 先说安装。Git 本身是一个命令行工具&#xff0c;无论你用的是 Windows、macOS 还是 Linux&#xff0c;安装方式都不太一样&#xff0c;但核心思路是一样的&#xff1a;装好之后&#xff0c…

作者头像 李华
网站建设 2026/10/9 3:14:47

Java Web学生信息管理系统源码实战:Servlet+JSP+MySQL完整解析

简介&#xff1a;这是一份基于Java Web的学生信息管理系统完整源码包&#xff0c;面向正在学习Java Servlet、JSP与JDBC的开发者&#xff0c;也适合用作课程设计或毕业设计的参考项目。系统围绕用户、学生、课程与成绩四大模块展开&#xff0c;实现登录验证、学生信息增删改查、…

作者头像 李华