Bruno 是一个开源、离线优先的 API 客户端(MIT 协议,GitHub 40k+ star)。和 Postman、Insomnia 最大的不同只有一条:你的接口集合是本地文件夹里的一堆纯文本文件,不是厂商云端的 JSON 大对象。不需要注册账号,不需要同步服务,接口集合和你的代码一起进 Git、一起走 Code Review。

一个真正带界面的实用软件——而且是每个做后端、做客户端联调的人都绕不开的那类工具。


一、为什么是它

用 Postman 的人多少都遇到过这几件事:

  • 换个电脑,集合在云端,但得登录、得等同步、离线时功能受限;

  • 团队协作要按人头买席位;

  • 集合导出是一个几万行的 JSON,Git diff 出来完全没法看,评审更无从谈起;

  • 内网 / 涉密环境不方便把请求 URL、Token 往第三方服务器上送。

Bruno 的设计取舍正好戳在这些点上:

对比项

Postman / Insomnia

Bruno

集合存在哪

厂商云端,绑定账号

本机文件夹里的纯文本

离线可用

受限或需登录

原生离线,完全可用

协作方式

专有云同步 + 席位

Git 分支、PR、评审

存储格式

专有导出 JSON

可读可 diff 的文本格式

隐私

请求与 URL 可能过厂商服务器

数据不出本机

价格

免费额度 + 订阅

MIT,核心功能免费开源

一句话概括它的价值:接口调试从"某个人云端账号里的一份资产",变成了"跟着代码走的工程产物"。


二、安装

它是带界面的桌面软件,Windows / macOS / Linux 三平台都有。

# macOS
brew install bruno

# Windows(三选一)
winget install Bruno.Bruno
choco install bruno
scoop install bruno

# Linux
sudo snap install bruno
flatpak install flathub com.usebruno.Bruno

也可以直接从官网下载安装包:.exe / .msi(Windows)、.dmg(macOS)、.AppImage / .deb / .rpm(Linux)。

Windows 用 MSI 批量部署的公司注意:可以用 msiexec /i Bruno.msi AUTOUPDATE_ENABLED=false 统一关掉自动更新。企业部署时这个参数挺有用。

配套的 CLI(跑 CI 用,跟桌面端是两个东西):

npm install -g @usebruno/cli
bru --version

装完打开就是工作区,没有注册、没有登录、没有引导广告——这不是功能缺失,是它的产品立场。


三、集合就是文件夹

Bruno 里一个 collection 就是一个目录,每个请求是一个文件:

my-api/
├── bruno.json              # 集合元信息(名称、忽略规则)
├── environments/           # 环境变量
│   ├── dev.bru
│   ├── staging.bru
│   └── production.bru
├── auth/
│   ├── login.bru
│   └── refresh-token.bru
├── users/
│   ├── list-users.bru
│   ├── get-user.bru
│   └── create-user.bru
└── orders/
    └── create-order.bru

目录结构就是你的 API 资源结构,文件夹怎么分、文件怎么命名,直接决定 Git diff 好不好读。建议一开始就把集合目录建在项目仓库里(比如 api-collection/),而不是随便找个桌面文件夹。

格式演进提醒:早期及目前主流的请求文件是 .bru 格式(下面的例子都是它),Bruno v4 起官方在推 OpenCollection YAML 格式(集合根目录会出现 opencollection.yml,环境文件变成 environments/local.yml)。老集合照常能用,官方也提供转换。两者思路一样:纯文本、可读、可 diff。


四、一个请求文件长什么样

这是 Bruno 最值得看的东西——一个完整的 POST 请求带鉴权、带断言:

meta {
  name: Create User
  type: http
  seq: 2
}

post {
  url: {{baseUrl}}/users
  body: json
  auth: bearer
}

headers {
  Content-Type: application/json
  X-Request-ID: {{$randomUUID}}
}

auth:bearer {
  token: {{authToken}}
}

body:json {
  {
    "name": "{{userName}}",
    "email": "{{userEmail}}"
  }
}

script:post-response {
  bru.setVar("createdUserId", res.body.id);
}

tests {
  test("状态码是 201", function() {
    expect(res.status).to.equal(201);
  });
  test("返回了用户 ID", function() {
    expect(res.body.id).to.be.a('number');
  });
}

三十来行,一个不懂这个工具的人也能看懂改了什么。这就是它能进 Code Review 的原因——换成 Postman 那种导出的 JSON,评审时你只会看到一坨 diff。

常用块一览:

作用

meta

请求名、类型、执行顺序 seq

get / post / put / delete

方法、URL、body 类型、鉴权方式

headers

请求头

params:query

URL 查询参数

body:json / body:graphql / body:multipart-form

请求体

auth:bearer / auth:oauth2

鉴权配置

script:pre-request / script:post-response

前置 / 后置脚本

assert / tests

断言与测试

vars / vars:secret

环境变量声明(仅环境文件)

环境文件(environments/dev.bru):

vars {
  baseUrl: http://localhost:3000/api
  userName: dev-user
}

vars:secret [
  authToken
]

vars:secret 里的变量名会被提交(团队知道有这么个变量),但值不落盘,存到本地的 .env 文件里,你把那个文件加进 .gitignore 就完事了。这是"团队知道有哪些密钥"和"密钥不进仓库"之间的一个很好平衡。


五、速查表

GUI 里最常用的

操作

说明

File → New Collection

新建集合,选一个目录

File → Open Collection

打开已有集合目录

集合右键 → New Request

新建请求

右上角环境下拉

切换 dev / staging / prod

Send / Ctrl+Enter

发送请求

集合右键 → Run

批量运行(集合运行器)

CLI

命令

说明

bru run

跑整个集合(在含 opencollection.yml / bruno.json 的目录里)

bru run 01-login.bru

只跑一个请求,也可跟多个文件或文件夹

bru run --env staging

指定环境

bru run --env staging --env-var baseUrl=http://localhost:3000

临时覆盖某个变量

bru run --tests-only

只跑带测试 / 断言的请求

bru run --reporter-html r.html --reporter-junit r.xml

生成报告(还支持 --reporter-json

bru run --bail

首次失败即中止

bru run --insecure / --cacert ca.pem

跳过 / 指定 TLS 校验

bru run --csv-file-path users.csv

数据驱动:按 CSV 每行跑一次

bru init my-api

命令行初始化集合

bru import postman collection.json

从 Postman 导入

bru import openapi --source <url> --output ./api

从 OpenAPI 规范生成集合

bru env list / bru env show staging

查看环境

脚本里能用的 API

写法

作用

bru.setVar("k", v) / bru.getVar("k")

运行时变量(同一次运行内传递)

bru.setEnvVar("k", v) / bru.getEnvVar("k")

环境变量

bru.setNextRequest("name")

指定下一个执行的请求

res.status / res.body / res.getBody() / res.responseTime

读取响应

{{$randomUUID}} / {{$timestamp}} / {{$guid}}

内置动态变量


六、五个真实场景

场景 1:登录拿 Token,后续请求自动带上

这是接口调试里最高频的痛点。在 auth/login.bru后置脚本里一句话解决:

script:post-response {
  if (res.status === 200) {
    bru.setEnvVar("authToken", res.body.token);
  }
}

之后所有请求的 auth:bearer 里都写 {{authToken}},登录一次,全集合畅通。再也不用每次手动复制 Token 粘到别的请求里。

场景 2:一套请求,三个环境

同一份集合,在 environments/ 下建 dev.brustaging.bruproduction.bru,各自的 baseUrl 写自己的值。GUI 里点下拉切换,CI 里用 --env staging

不要在请求里硬编码 URL——这是新手最常犯的错,一旦硬编码,换环境就得逐个文件改。

场景 3:密钥留在本地,不进仓库

vars:secret [
  authToken
  apiKey
]

变量名进 Git(同事知道集合依赖哪些密钥),值写进本地 .env.env.gitignore。再配合 bru run --env-file .env.local 在 CI 里注入。

看着不起眼,但这是很多团队从 Postman 迁到 Bruno 的直接动机——接口调试不该意味着把生产 Token 托管在别人服务器上。

场景 4:CI 里跑接口回归测试

npm install -g @usebruno/cli
bru run --env ci \
  --reporter-html reports/api-test.html \
  --reporter-junit reports/api-test.xml

JUnit 格式能被绝大多数 CI(Jenkins、GitLab CI、GitHub Actions)直接解析成测试报告;HTML 报告丢成构建产物,出问题时直接翻。官方也提供 CLI 的 Docker 镜像,CI 里不想装 Node 就直接用镜像跑。

场景 5:从 Postman / OpenAPI 迁过来

# 从 Postman 导出的集合文件导入
bru import postman collection.json

# 直接照着线上 OpenAPI 规范生成一整套集合
bru import openapi \
  --source https://petstore3.swagger.io/api/v3/openapi.json \
  --output ./petstore-api \
  --collection-name "Petstore API"

第二条尤其香:你的后端只要维护了 OpenAPI 文档,就能一键生成可跑的接口集合,文档和调试工具不会脱节。


七、和其他工具怎么选

工具

形态

适合谁

Bruno

桌面端 + CLI

想要 Git 原生、离线、数据不外流,团队一起评审接口改动

Postman

桌面端 + 云端

已经在用、团队习惯云同步、需要大量非开发同学参与

Apifox

桌面端 + 云端

中文团队,想要"设计 + 调试 + Mock + 自动化测试"一体化

Insomnia

桌面端

轻量调试,但存储模型不是纯文本

Hoppscotch

浏览器网页

临时调试、不想装客户端

curl / xh / httpie

命令行

一次性请求、写脚本

我的建议:个人或小团队、代码放 Git、在意数据隐私 → 直接上 Bruno;需要产品、测试、后端一起在线协作、要 Mock 服务 → Apifox 这类一体化平台更省事。两者不冲突,不少人 Bruno 负责日常调试、平台工具负责团队协作。


八、常见坑

现象

原因 / 解法

CLI 报错说找不到 bru

CLI 是独立的 npm 包,装了桌面版不等于有 CLI,要 npm install -g @usebruno/cli

请求里 {{baseUrl}} 没被替换

没选环境(GUI 下拉 / CLI 的 --env),或变量名拼错

批量运行时顺序不对

依赖 meta 块里的 seq;有链式依赖时忘了设 seq,执行顺序就是随机的

脚本里 require 外部 npm 包报错

CLI v3.0.0 起默认从 Developer Mode 改为 Safe Mode,需要显式加 --sandbox=developer

变量值明明设了却读不到

bru.setVar(运行时变量)和 bru.setEnvVar(环境变量)作用域不同,混用会踩坑

手动编辑 .bru 文件后解析失败

格式对空白敏感,尤其 body:json 块内;建议改结构用 GUI,改值才手编

敏感变量提交进仓库了

检查是不是漏了 vars:secret,或 .env 没加进 .gitignore


九、上手路径

  1. 第一天:装好桌面端,在项目仓库里建一个 api-collection/ 目录当集合,把最常用的 3 个接口建进去;

  2. 第二天:把接口里的 URL 全部抽成变量,建 dev / staging 两个环境文件;

  3. 第一周:写一个登录请求,在后置脚本里 setEnvVar 存 Token,让其余请求自动带上;

  4. 第二周:给关键接口补 tests 断言,在 GUI 里用集合运行器跑一遍;

  5. 再往后:装 CLI,把 bru run --env ci --reporter-junit 接进流水线,接口测试正式变成工程的一环。

一句话总结:如果你在意"接口调试产物该不该进版本库",Bruno 给出的答案是——该,而且它本来就该是一堆能读能 diff 的文本文件


参考

  • 官网:https://www.usebruno.com/

  • 下载与安装:https://docs.usebruno.com/get-started/bruno-basics/download

  • 官方快速上手教程:https://blog.usebruno.com/bruno-tutorial

  • CLI 文档:https://docs.usebruno.com/bru-cli/overview

  • 从 Postman 迁移:https://docs.usebruno.com/get-started/import-export-data/import-collections

  • 源码:https://github.com/usebruno/bruno

本文基于 2026 年 Bruno 官方文档与 v4 版本编写。v4 起官方在推行 OpenCollection YAML 格式,旧 .bru 集合仍可用,具体细节以官方文档为准。