开源API客户端Bruno:轻量、离线优先、Git友好的Postman替代方案
2026/9/16 6:19:37 网站建设 项目流程

1. 上手体验与核心亮点

1.1 从 Postman 迁移过来的第一感受

先说结论:我用 Postman 做了三年多接口调试,从 8.x 一路用到 12.x,日常就是对着几十个接口来回测试。Postman 越来越重的体积大家心里都有数,每次冷启动少说等个三五秒,有时候赶上自动更新重启,那种等待的烦躁感几乎成了肌肉记忆。

后来在 GitHub 上刷到一个叫 Bruno 的开源项目,口号直接就是“离线优先的 API 客户端”。我第一反应是不太信,毕竟这年头“轻量”“极速”都成了营销话术。但看到一个关键数字:安装包 10 MB 级别,启动不到 1 秒,这就有点意思了。我当天就下载装好,把项目里最常用的十几个接口配置搬过去实测了一周。

体验最直接的三个点:

  • 启动真的快。双击图标到界面完全可交互,体感在 1 秒以内,基本是秒开,完全没有 Postman 那种转圈等待。
  • 存数据不需要登录。Bruno 的集合不是存在云端,而是直接以文本文件形式保存在你本地指定的文件夹里,天然离线可用,连账号注册这步都省了。
  • 集合跟着 Git 走。每个请求文件都是纯文本格式,可以直接放进 Git 仓库,团队里谁改了接口、改了什么参数,看 diff 一目了然。

这三个点组合在一起,解决的其实是很多开发者面对 Postman 时没说出口的痛点:账号体系太繁琐、团队协作数据不透明、启动越来越慢、免费版功能还不断被切分。Bruno 用一套更简单、更符合程序员直觉的方式把这些事一次性处理掉了。

1.2 适合什么样的人换过来

先说结论,不是所有人都适合立刻换。如果你是纯重度用户体验派,依赖 Postman 的云端分享、团队工作空间、各种丰富的插件生态和界面动画效果,那 Bruno 目前的形态可能偏朴素。但如果你是以下这几类人,Bruno 值得认真试试:

  • 后端开发者:每天面对大量测试接口变更,需要快速改 Header、改 Body、重新发送,对启动速度和响应速度敏感。
  • 前端开发者:想看清楚请求细节、抓接口返回做联调,又不想被 Electron 系工具的沉重感拖累。
  • 团队协作场景:项目仓库里直接带上接口集合,新人克隆项目后打开 Bruno 就能看到全部接口,不需要花钱买协作版,也不需要额外同步步骤。
  • 习惯用 Git 管理一切的技术人:接口定义和数据环境本身就是代码的一部分,天然适合走代码评审流程。
  • 注重隐私和数据安全的场景:所有数据留在本地,不经过第三方服务器,适合内网开发或对数据出域有要求的项目。

我的判断是,Bruno 的目标用户本来就是“认为接口定义应该和代码走同一套版本管理”的那批人。它的设计哲学是替开发者省掉那些和后端能力无关的流程负担,把精力留在改接口本身这件事上。

2. 核心细节解析:为什么它敢说自己“轻”且“快”

2.1 技术底子和启动速度的真相

先说一个容易误导人的点:10 MB 的安装包和“启动不到 1 秒”这两个宣传点放在一起,容易让人误以为它是用 C++ 或 Rust 写的原生命令行工具。实际上 Bruno 用的是 Electron 框架,底层是 Chromium 加 Node.js,本质上还是个“内置浏览器”的桌面应用。

那它为什么还能启动这么快?关键在于两点:

第一,安装包体积控制,Electron 应用把运行体积压到 10 MB 级别确实少见,说明作者对打包依赖做了非常克制的裁剪。界面没有塞动画库、没有重型 UI 框架,渲染层很干净。第二,启动逻辑极简,没有开机自检、没有自动更新检测、没有运行时的数据同步等待,打开应用只是加载本地文件索引,不需要跟服务器握手。没有多余的远程依赖,启动自然快。

这和 Postman 形成了鲜明对比。Postman 虽然是老牌工具,功能覆盖面广,但多年来积累的功能模块、插件机制、账号体系、云端同步服务,导致它每次启动都要加载大量模块,等待时间越来越长。从纯工具理性角度看,Bruno 是“只做一件事,做精做透”的代表。

我这个观点可能有争议,但在日常接口调试场景下,工具的开箱速度和流畅程度,比多几个不常用的高级功能重要得多。高频操作的收益是实打实的。

2.2 数据的存储逻辑和 Git 友好设计

Bruno 的集合目录结构是这样的:

collection-name/ ├── bruno.json ├── environments/ │ ├── local.bru │ └── prod.bru └── requests/ ├── login.bru ├── get-user-info.bru └── update-password.bru

每个.bru文件的内部结构是可读的文本格式,类似 Markdown 搭配键值对。比如一个简单的 GET 请求长这样:

meta { name: Get User Info type: http seq: 2 } get { url: http://localhost:3000/api/users/{{userId}} body: none auth: none } headers { Accept: application/json X-Request-Id: {{$uuid}} }

这套设计理念和 Postman 完全不同。Postman 的集合数据是一个 JSON 文件,里面所有请求嵌套在一棵大 JSON 树里,人直接看根本摸不清谁是谁,diff 时只能看到整坨 JSON 变化。Bruno 则把一个集合切碎成一个个独立的文本文件,改哪个请求就改哪个文件,diff 结果准确到单行。

实际操作中,用 Git 管理接口集合的体验很像在管理一份代码目录。团队里有人改了登录接口的请求参数,提交的 MR 里能清清楚楚看到:

- Authorization: Bearer {{oldToken}} + Authorization: Bearer {{newToken}}

这个特性对技术团队的诱惑力极其致命。你把接口测试的资料从“工具里的私有数据”变成了“仓库里的公开资产”,接口的演进历史、修改理由、操作人,全部沉淀在 Git 历史里。

2.3 为什么“离线优先”反而是加分项

Bruno 的“离线优先”不是说不能联网,而是指它的核心能力不依赖任何云端服务。你用 Postman 时,如果不登录账号就提示功能受限,数据要强制绑定账号才能跨设备同步。Bruno 把账号体系整个砍掉,数据和配置只存在你指定的目录,团队同步靠 Git,跨设备同步靠克隆仓库。

从原理上看,这是把接口调试工具从“SaaS 服务”拉回到了“单机软件”的定位。但在这个数据敏感度越来越高的时代,本地优先反而成了优势。我所在的项目组有阵子涉及内部系统的对接,接口信息本身就有一定敏感性,拿 Postman 调试总得考虑数据是不是经过了别人的服务器。换成 Bruno 后,这个顾虑直接消失,因为所有数据从物理位置上就没有离开过你的电脑。

当然这也带来了一个必须接受的代价——没有云备份。数据安全需要你自己负责,比如定期提交 Git、养成及时 push 的习惯。这个取舍我认为非常划算,毕竟接口数据同步 Git 本身就是团队开发流程的一部分,云同步反而多出一步人工操作。

3. 实操过程与核心配置指南

3.1 从下载到创建第一个接口请求的完整流程

安装 Bruno 的路径不多,但都比较直接。我主要推荐两种方式:

  • 官网下载安装包:Bruno 官网根据系统版本直接下载,支持 Windows、macOS、Linux 三端。Linux 用户还能选.deb.rpm或 AppImage 格式。
  • 命令行安装(软件包管理器):macOS 用户可以直接用brew install brunohq/tap/bruno;Windows 用户可以用winget install Bruno.Bruno;Linux 上依发行版不同也可以用对应的包管理器安装。

安装完成后,首次打开是空白的集合界面,这时候不要急着点“创建”,先建一个项目文件夹,把接口数据存在一个专门目录里。像这样规划:

project-api/ ├── api-tests/ // 存放 Bruno 集合 └── docs/ // 可选,放接口说明文档

接下来正式创建集合。在 Bruno 主界面点击“New Collection”,给它起个名字,然后选择存放目录。我建议直接把目录选在项目仓库根目录下,这样待会儿 Git 提交时就能一起带上去。

创建完集合后,在里面新建第一个请求。这时会看到界面右边有个编辑器,左侧是请求方法选择,默认是 GET,还有 URL 输入框、Headers、Params、Body、Auth 等 Tab。整个过程和 Postman 的操作习惯保持了一致,从 Postman 迁移过来的学习成本很低,熟练的人可能五分钟内就能完成首次配置。

第一个接口用最简单的登录接口为例:

post { url: http://localhost:8080/api/auth/login body: json auth: none } body:json { { "username": "{{adminUser}}", "password": "{{adminPassword}}" } }

保存完这个请求,你会看到requests目录下生成了一个login.bru文件,用编辑器打开就能看到类似上面的纯文本格式。

3.2 环境变量与多环境切换的配置方法

刚开始用 Bruno 时最容易忽略的就是环境变量功能,后面接口多了才发现这步省不掉。

Bruno 的环境变量设计思路和 Postman 不太一样。Postman 把环境变量做成“一组一组的全局选择”,Bruno 则是在集合目录下专门创建一个environments文件夹,里面每个.bru文件对应一组环境。举例来说:

environments/ ├── local.bru ├── dev.bru └── prod.bru

每个环境文件的内容也很简单:

vars { host: http://localhost:8080 adminUser: test_admin adminPassword: 123456 token: }

然后在请求文件里,通过{{host}}{{adminUser}}这样的双花括号语法引用变量。发送请求前,在界面右上角的下拉菜单切换对应的环境文件,Bruno 会动态替换变量值。

需要注意的一个坑:环境文件里不要提交真实密码或 token 到 Git。建议把prod.bru里的敏感字段留空,在本地手动补全,然后通过.gitignore忽略一些关键环境文件,或者只在本地创建local.bru

Bruno 还内置了一些动态变量,在请求拼接时很常用。比如:

  • {{$uuid}}:生成随机 UUID。
  • {{$isoTimestamp}}:生成当前 ISO 格式时间戳。
  • {{$randomInt}}:生成随机整数。
  • {{$envVarName}}:读取当前环境变量。

这些变量能直接用在 URL、Header、Body 里,类似 Postman 的预定义变量,不需要额外写脚本,简单又实用。

3.3 测试脚本与数据提取

接口调试不可能只做手动发送、肉眼检查返回结果,测试脚本是刚需。Bruno 的脚本体系跟 Postman 类似,分成两部分:

  • 请求前脚本(Script):发送请求前执行,常用于设置签名、生成时间戳、动态计算参数。
  • 请求后脚本(Tests/Assertions):收到响应后执行,用于断言状态码、提取返回数据赋给环境变量。

两者在界面上的入口分别是 Pre-Request 和 Tests 两个 Tab,都用 JavaScript 编写,引擎基于 Node.js。

最常用的请求后脚本,比如断言状态码:

const res = bru.getRes(); bru.assert(res.status === 200, "Expected 200 OK");

提取登录返回的 token 并写入环境变量:

const res = bru.getRes(); const data = res.body; if (data && data.token) { bru.setEnvVar("token", data.token); bru.log("Token saved: " + data.token); }

这两段脚本覆盖了绝大部分接口联动场景:先登录拿 token,再把 token 作为后续接口的 Authorization 头。Postman 里的pm.response.to.have.status(200)写法在 Bruno 里是bru.assert(条件, 描述)语法,逻辑一致但略有不同,迁过来时需要顺手改一下。

请求前脚本可以用来处理签名参数。举个例子,有个接口需要的签名值是当前时间戳加盐后的 MD5,那就在 Pre-Request 里写:

const Md5 = require("md5"); const timestamp = Date.now(); const salt = "my-secret-salt"; const sign = Md5(timestamp + salt); bru.setVar("timestamp", timestamp.toString()); bru.setVar("sign", sign);

然后请求体的 URL 参数里直接用{{timestamp}}{{sign}}。Bruno 会自动按顺序执行请求前脚本、发起请求、执行请求后脚本,整个流程和 Postman 的 Interceptor 机制是同一路由子。

3.4 使用命令行工具做接口回归测试

Bruno 除了图形界面外,还提供了一套命令行的 CLI 工具,这是很多 Postman 免费用户羡慕已久的能力。把接口集合变成自动化测试集,不需要额外写测试框架,直接命令行跑:

安装 Bruno CLI:

npm install -g @usebruno/cli

在集合目录下运行测试:

bru run . --env local

命令行工具会按照集合里每个请求文件的顺序依次执行,执行完输出每个接口的状态码、耗时、断言结果。也可以指定只跑某个文件夹:

bru run requests/login.bru --env local

还有个实用的参数:--reporter-html,能生成一份 HTML 格式的测试报告:

bru run . --env dev --reporter-html --output ./reports

这个功能最大的价值是可以接入 CI。在 GitLab CI 或 GitHub Actions 里拉完代码后自动跑一轮接口测试,接口挂了就直接拦截合并请求,测试结果作为构建产物留存。这比起在 Postman 里单独建 Collection 再通过 Newman 执行,流程上更顺滑,因为数据源和代码在同一个仓库,天然不需要额外导出动作。

4. 常见问题与避坑指南

4.1 中文乱码和接口请求头编码问题

国内开发者最常遇到的就是接口返回 JSON 里的中文乱码。这个问题和 Bruno 本身关系不大,大多是请求时没有声明正确的请求编码,或者服务端没有正确返回 Content-Type。

排查思路如下:

  • 检查请求头是否设置了Accept: */*Accept: application/json,确保服务端知道客户端期望 JSON 格式。
  • 如果接口返回的非 JSON 内容是 HTML 乱码,可能是服务端返回了text/html编码格式,需要在脚本里手动解码。
  • 检查环境变量里的值是否包含中文,如果值是从其他文件粘贴过来的,确认源文件保存的编码是 UTF-8,不要用 GBK。

实际踩坑最多的场景是:在 Windows 上用记事本新建环境文件,默认可能保存为 ANSI 编码,Bruno 按 UTF-8 读取时中文字符就乱码了。解决方式是用 VS Code 等编辑器重新保存为 UTF-8 编码。

如果接口本身传参就需要 GBK 编码,Bruno 并没有直接在界面上提供编码转换选项,这种场景我建议在请求前脚本里用 JavaScript 做手动编码转换。虽然麻烦,但这类接口在正规系统里越来越少见了。

4.2 登录态依赖怎么串联

多接口测试时最常见的问题是:登录后拿到的 token,怎么自动传给后续每个接口?

Bruno 的变量作用域需要理解清楚。用bru.setVar()设置的变量是当前请求级别;用bru.setEnvVar()设置的是环境级别,在当前环境文件内全局生效。后续接口只要引用{{token}},Bruno 就会从环境文件绑定的变量集合里查找值。

推荐的做法是单独建一个auth-login.bru请求文件,专门用来执行登录操作。然后在后续所有需要 token 的请求文件里,Auth 类型选择 Bearer Token,Token 值填{{token}}

有个细节容易忽略:切换环境后,之前通过setEnvVar写入的变量是动态写入内存的,不会自动保存到.bru环境文件里。你要是重启了 Bruno 工具,再切一次环境,token 变量可能就丢了。解决办法是把 token 初始值在环境文件里留一个过期占位符,然后在请求前脚本里加一个判断:如果 token 为空才执行登录请求。这样保证每次运行测试集都能自动完成登录、后续接口自动携带最新 token,而不是手动画刷新 token。

4.3 自动化测试时的执行顺序问题

Bruno 默认按照请求文件名的字母顺序执行测试集,而不是按照你在界面上拖拽的顺序。这在跑多个请求相互依赖的测试链时非常容易出错。

举个例子:你有login.bruget-user.bru,如果登录接口的名称起成api-login.bru,按字母排序它会排在get-user.bru前面,执行顺序没问题。但如果起名是login.bru,它排在后面,那 get-user 先跑时 token 还没初始化,断言必然挂。

解决办法有两种:

第一种是文件名用序号前缀,比如01-login.bru02-get-user.bru03-update-password.bru,这是最暴力也最直观的方法。 第二种是在bruno.json里显式配置执行顺序,或者在请求文件里通过seq字段设置序号:

meta { name: Login type: http seq: 1 }

seq数值越小越先执行。我实测下来,自己项目中用的是混合方案:核心链路接口用序号前缀,其余零散请求不强制排序,保持文件夹的自然组织。

4.4 和 Postman 的兼容性以及数据迁移问题

从 Postman 迁过来的时候最担心的是历史数据怎么搬。Bruno 在较新版本里原生支持导入 Postman Collection(v2.1 标准格式)。路径是:主界面右上角菜单,选择 Import,然后选择 Postman Collection 的 JSON 文件即可。

导入后有两个常见情况需要注意。第一,Postman 里用{{variable}}的地方,Bruno 基本能正确映射到环境变量,但部分 Postman 特殊的脚本变量(比如pm.collectionVariables)不会自动转换,需要手动在脚本里改成bru.getEnvVarbru.setEnvVar。第二,Postman 里的断言脚本写在 Tests 标签页,导入时 Bruno 会尝试转换,但pm.test的语法不会自动变成bru.assert,需要手动改。

我自己当时迁移时总共搬了二十多个接口文件,机械性的部分一首歌的时间就完成了,真正花时间的是逐个检查脚本里的 API 调用差异,特别是签名逻辑和 token 提取逻辑。

另外提一句:Bruno 完全支持从 OpenAPI/Swagger 文件导入集合。如果你手头有最新的 OpenAPI 文档,直接用这个方式重新生成集合比从 Postman 搬更干净。

4.5 插件生态还不完善时的替代方案

对习惯了 Postman 各种 Marketplace 插件的人来说,Bruno 目前的插件生态显得比较朴素,官方主要以内置能力为主。好在开发者可以在脚本里引用 npm 包,等于保留了一个口子。

举个例子,接口测试里需要用到加密算法,Postman 生态里可以搜到现成的库。Bruno 的解决办法是直接引入 npm 包:

const CryptoJS = require("crypto-js"); const hash = CryptoJS.HmacSHA256(message, secret); const hexHash = CryptoJS.enc.Hex.stringify(hash); bru.setVar("sign", hexHash);

前提是脚本运行时环境支持 require,实测 Bruno 的脚本引擎是支持 CommonJS 模块引用的。不过要注意,安装第三方依赖需要修改集合目录下的package.json或者在集合目录里执行npm install,然后 Bruno 会从node_modules里解析依赖。

还有一个容易踩的坑:在写脚本require("path")require("fs")访问本机文件系统时,Bruno 出于安全考虑做了限制,部分 Node.js 内置模块不可用。如果需要在脚本里读写文件做数据准备,建议改用bru.getFile方法来读取文件内容。具体的 API 细节以官方文档为准,版本间可能有差异。

5. 写到最后的一点实际建议

Bruno 目前在接口调试工具里算是一股清流。它不追求大而全,而是把接口集合的管理方式拉回到了开发者最喜欢的“文本文件 + Git + 命令行”时代,用最朴素的方式解决了 Postman 在统一协作上积累的复杂度问题。

改用 Bruno 后,我最大的感受其实是“心理负担变小了”。之前调接口,打开 Postman 意味着打开一个要等待启动的重量级应用,还要面对时不时弹出的更新提示。现在打开 Bruno,像打开一个文本编辑器一样轻松,数据就在本地文件夹里,改接口测试用例就像改代码一样自然。

如果你还在犹豫要不要从 Postman 迁过来,我的建议是:从一个小项目开始试水。用 Bruno 跑一遍你的核心接口链路,感受一下启动速度和 Git 管理机制,再决定要不要全量迁移。工具这东西,最终还是要服务于人,用得顺手、省心提效,就是好工具。Bruno 在这一点上,确实做到了 Postman 很长时间都没有做到的事。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询