一、为什么要离开 Postman?
Postman 作为 API 调试领域的标杆,曾是每个后端开发者的必备工具。但近两年情况急转直下:
1.1 强制登录与数据云端化
Postman v10 之后强制要求登录账号,"离线模式"被大幅削弱——本地集合默认同步到 Postman 云端。对公司内网开发、安全合规要求严苛的团队而言,这是致命一击。对个人开发者而言,近因网络扰动导致无法登录不能使用的情况时有发生。
1.2 性能越来越臃肿
一个 Electron 应用动辄吃掉 1-2GB 内存,启动时间超过 10 秒。对比之下 VS Code 都比它轻快。
1.3 功能受限与付费加压
免费版集合协作人数降至 3 人,Scratch Pad 模式功能残缺(不支持环境变量、脚本等)。想好好用?请付 $12/人/月。
这些变化让我开始寻找替代方案。
二、候选工具调研
2.1 候选清单
| 工具 | 定位 | 仓库地址 |
|---|---|---|
| Bruno | 本地优先、纯文本、Git 友好 | github.com/usebruno/bruno |
| Hoppscotch | Web 端优先的轻量客户端 | github.com/hoppscotch/hoppscotch |
| Insomnia | 老牌 REST/GraphQL 客户端 | github.com/Kong/insomnia |
| httpYac | VSCode 插件,纯文本定义 | github.com/AnWeber/httpyac |
| VS Code REST Client | 编辑器内 .http 文件 | 内置扩展 |
2.2 评估维度与对比
| 维度 | Bruno | Hoppscotch | Insomnia | httpYac |
|---|---|---|---|---|
| 本地优先 | ✅ 纯本地 | ⚠️ 默认 Web | ⚠️ 需账号 | ✅ VSCode 内 |
| Git 友好 | ✅ 纯文本 Bru 格式 | ❌ JSON 导出 | ❌ JSON 导出 | ✅ .http 文件 |
| 离线可用 | ✅ 完全离线 | ✅ 自部署可行 | ⚠️ 功能受限 | ✅ |
| 脚本支持 | ✅ JS (Bruno 语法) | ✅ JS (Pre-request) | ✅ JS | ✅ JS |
| 环境变量 | ✅ | ✅ | ✅ | ✅ |
| 导入 Postman | ✅ 一键导入 | ✅ | ✅ | ❌ 需转换 |
| 桌面端 | ✅ 原生应用 | ❌ 浏览器/自部署 | ✅ | ✅ VSCode |
| 开源协议 | MIT | MIT | Apache 2.0 | MIT |
| Star 数 | 30k+ | 68k+ | 35k+ | 1.5k |
2.3 关键决策点
经过实际试用,以下三点使我锁定了 Bruno:
1. 本地优先 + Git 原生支持
接口集合存储为纯文本 .bru 文件,结构清晰可读,天然支持 Git diff、code review、版本回滚。团队协作不再依赖第三方云,代码仓库即接口仓库。
2. 完全离线,无账号体系
没有强制登录,没有数据上传,内网环境、隔离环境照样用。这对安全敏感型团队是决定性优势。
3. 原生桌面应用,轻量快速
Bruno 用 Go + React 构建,启动秒开,内存占用远低于 Postman。虽然 2025 年开源版转为有限功能、商业版需要购买,但免费版的日常调试能力依然完整。

Bruno 这界面和postman是真像,布局操作按钮也差不太多

三、迁移实操:从导入到脚本改造
3.1 导入 Postman 集合
Bruno 官方提供了一键导入入口:
- 打开 Bruno,点击 Import Collection
- 选择 Postman Collection(支持
.json导出文件) - 选择导出的 Postman 集合文件,点击导入
导入后,每个 Postman 请求会转换成对应的 .bru 文件,目录结构基本保留。

注意:postman导出集合时变量不会完整的跟着导出,导入Bruno后自己比对原来集合的变量,缺的手动补齐。

3.2 环境变量迁移
Postman 的环境变量(Environment)需要在 Bruno 中重建:
- Postman:
{{base_url}}语法,配合 Environment 文件 - Bruno:同样支持
{{base_url}}语法,但环境定义在 Collection 级别
在 Bruno 中,点击集合右上角 Settings → Environment Variables,逐条添加:
{
"base_url": "https://api.example.com",
"username": "your_username",
"password": "your_password",
"token": ""
}
3.3 脚本迁移差异
这是迁移过程中工作量最大的部分。Postman 的 Pre-request Script / Tests 脚本需要改造成 Bruno 语法。
差异 1:变量读写
| 操作 | Postman | Bruno |
|---|---|---|
| 读取集合变量 | pm.collectionVariables.get("key") | bru.getCollectionVar("key") |
| 写入集合变量 | pm.collectionVariables.set("key", val) | bru.setVar("key", val) |
| 读取全局变量 | pm.globals.get("key") | bru.getVar("key") |
| 设置环境变量 | pm.environment.set("key", val) | bru.setEnvVar("key", val) |
差异 2:发送请求
Postman 用回调式,Bruno 用 async/await:
// Postman 回调式
pm.sendRequest(url, function (err, response) {
console.log(response.json());
});
// Bruno async/await
const res = await bru.sendRequest({ url: url, method: 'GET' });
console.log(res.data);
差异 3:内置库依赖
Postman 提供 require() 能力(如 moment、lodash),Bruno 不支持 require(),需用原生 JS 替代:
// Postman
var moment = require('moment');
var ts = moment().valueOf();
// Bruno(原生替代)
var ts = Date.now();
3.4 案例:登录获取 Token
Postman 原始脚本:
const baseurl = pm.collectionVariables.get('url_online');
const tokenurl = baseurl + "/api/v1/login/token";
pm.sendRequest({
url: tokenurl,
method: "POST",
header: {
"Content-Type": "application/x-www-form-urlencoded"
},
body: {
mode: "urlencoded",
urlencoded: [
{ key: "username", value: pm.collectionVariables.get("username") },
{ key: "password", value: pm.collectionVariables.get("password") }
]
}
}, function (err, response) {
const res = response.json();
pm.collectionVariables.set("token", res.access_token);
});
Bruno 改造后:
const baseurl = bru.getCollectionVar('url_online');
const tokenurl = baseurl + "/api/v1/login/token";
console.log("tokenurl:" + tokenurl);
const res = await bru.sendRequest({
url: tokenurl,
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded"
},
data: {
"username": bru.getCollectionVar("username"),
"password": bru.getCollectionVar("password")
}
});
console.log(res.data);
console.log(res.data.access_token);
bru.setVar("token", res.data.access_token);

3.5 案例: SM2/SM3 签名
我的原脚本使用 Postman 的 require('moment') 和大量 pm.collectionVariables.set()。
迁移要点:
// 1. 变量写入全部改为 bru.setVar
bru.setVar('sm3Hash', digest);
bru.setVar('sm2Sign', signature);
bru.setVar('sm2Enc', cipher);
bru.setVar('sm2Dec', plain);
// 2. moment 替换为原生 Date
// var moment = require('moment');
// var ts = moment().valueOf();
var ts = Date.now();
bru.setVar("ts", ts);
// 3. 生成签名方法不变
const sign = sm2.doSignature(signData, sm2prikey, {
hash: true,
publicKey: sm2pubkey,
der: true
});
// 4. Hex → Base64
const hexSign = sign;
const byteArray = [];
for (let i = 0; i < hexSign.length; i += 2) {
byteArray.push(parseInt(hexSign.substr(i, 2), 16));
}
const base64Sign = btoa(String.fromCharCode.apply(null, byteArray));
bru.setVar("cqvip-sign", base64Sign);

四、迁移常见报错与解决
4.1 ReferenceError: 'pm' is not defined
原因:脚本中残留 Postman 的 pm 全局对象。
解决:全文搜索 pm.,替换为 Bruno 对应 API(bru.getVar / bru.setVar / bru.getCollectionVar 等)。
4.2 ReferenceError: require is not defined
原因:Bruno 脚本环境不支持 Node.js 的 require()。
解决:用原生 JS 实现替代:
| 原用途 | 替代方案 |
|---|---|
require('moment') | Date.now() / new Date() |
require('lodash') | 原生数组/对象方法 |
require('crypto-js') | Bruno 内置加密或手写实现 |
4.3 bru.sendRequest 返回 undefined
原因:仍在使用 Postman 的回调式写法。
解决:改用 await 直接接收返回值:
// 错误(回调式)
bru.sendRequest(url, function(err, res) { ... });
// 正确(async/await)
const res = await bru.sendRequest({ url: url, method: 'GET' });
4.4 动态加载外部 JS 库失败
Postman 中可以 eval 远程 JS,Bruno 中此方案受限。建议将依赖的算法库代码直接内联到脚本中,或改用 Bruno 支持的加载方式。

五、迁移后的收益
经过完整迁移,团队在以下方面获得明显改善:
- 接口即代码:
.bru文件随业务代码一起纳入 Git 管理,接口变更可追溯、可 review - 零云依赖:彻底摆脱账号和云端同步,内网/隔离环境畅通无阻
- 启动提速:从 Postman 的 10 秒到 Bruno 的秒开,日常调试体验显著提升
- 内存友好:内存占用下降一个数量级,显著缓解开发机寸土寸金的内存紧张问题

六、总结
| 阶段 | 关键动作 |
|---|---|
| 调研 | 明确"本地优先、Git 友好、离线可用"三大需求 |
| 选型 | 对比 5+ 工具,锁定 Bruno |
| 迁移 | 一键导入集合 → 重建环境变量 → 改造脚本 |
| 踩坑 | 解决 pm、require、回调式三大差异 |
| 收益 | 接口 Git 化、零云依赖、性能提升 |
Postman 到 Bruno 的迁移,本质上是**从"云优先"回到"本地优先"**的转变。对重视数据主权、追求轻量高效的团队而言,这是一条值得走的路。

300

被折叠的 条评论
为什么被折叠?



