Bruno: The Open-Source Postman Alternative That Stores Collections in Git
Postman was the default API testing tool for years. Then it required a cloud account, started syncing collections to its servers, and became slower and heavier with each release. Bruno is the reaction: an offline-first API client that stores your collections as plain text files you can commit to git like any other code.
What Makes Bruno Different
The core difference: Bruno stores collections as files on disk, not in a cloud database.
Each request is a .bru file in your project directory:
my-api/
├── auth/
│ ├── login.bru
│ └── refresh.bru
├── users/
│ ├── list-users.bru
│ └── create-user.bru
└── bruno.json
This means:
- Your API requests live in your git repository alongside your code
- Team members share requests via the same git workflow as code (PRs, branches, reviews)
- No account required, no cloud sync, no subscription
- Works completely offline
- Fast — no Electron cloud sync overhead
Installing Bruno
macOS:
brew install bruno
# Or download from usebruno.com
Linux:
# Debian/Ubuntu
wget -qO- https://apt.usebruno.com/key.gpg | sudo gpg --dearmor -o /usr/share/keyrings/bruno.gpg
echo "deb [signed-by=/usr/share/keyrings/bruno.gpg] https://apt.usebruno.com stable main" | sudo tee /etc/apt/sources.list.d/bruno.list
sudo apt update && sudo apt install bruno
Windows: Download the installer from usebruno.com.
The .bru File Format
Bruno uses its own .bru format for storing requests. It's readable, diffable text:
meta {
name: List Users
type: http
seq: 1
}
get {
url: {{baseUrl}}/api/users
body: none
auth: bearer
}
headers {
Accept: application/json
}
auth:bearer {
token: {{authToken}}
}
This format is designed to be human-readable and git-friendly. Diffs make sense, merges work, and you can see exactly what changed.
Environments and Variables
Bruno uses environments for managing variables like base URLs and auth tokens:
Create an environment: Click "Environments" → "Create Environment" → add variables.
// .env file (gitignored — for secrets)
{
"authToken": "your-dev-token"
}
// environments/local.bru (committed — for non-secrets)
vars {
baseUrl: http://localhost:3000
}
Variable syntax: Use {{variableName}} anywhere in the request — URL, headers, body, auth.
Multiple environments: Create local, staging, and production environments with different baseUrl values. Switch with a click.
Secret management: Variables marked as "secret" are stored locally and never committed to git, keeping credentials out of your repository.
Request Scripting
Bruno supports JavaScript scripts that run before or after requests:
Pre-request script (runs before sending):
// Auto-refresh token if expired
const token = bru.getVar("authToken");
const expiry = bru.getVar("tokenExpiry");
if (Date.now() > expiry) {
// Trigger token refresh
bru.setVar("authToken", newToken);
}
Post-request script (runs after receiving):
// Save token from login response
const token = res.getBody().data.token;
bru.setVar("authToken", token);
bru.setVar("tokenExpiry", Date.now() + 3600 * 1000);
This allows chaining requests — log in, capture the token, use it automatically in subsequent requests.
Assertions and Testing
Bruno includes a testing framework for validating responses:
// In the Tests tab of a request
test("Status is 200", function() {
expect(res.getStatus()).to.equal(200);
});
test("Response has users array", function() {
const body = res.getBody();
expect(body.data.users).to.be.an("array");
expect(body.data.users.length).to.be.greaterThan(0);
});
test("Response time under 500ms", function() {
expect(res.getResponseTime()).to.be.lessThan(500);
});
Run all tests in a collection from the command line:
npm install -g @usebruno/cli
bru run --env local
This makes Bruno suitable for CI/CD pipelines — run your API tests as part of your test suite.
Bruno CLI
The CLI enables automated testing and CI integration:
# Run all requests in a folder
bru run ./auth --env staging
# Run with a specific environment
bru run ./users/list-users.bru --env production
# Output results as JSON
bru run . --env local --output results.json
# Reporter format (junit for CI integration)
bru run . --env local --reporter junit --output junit-results.xml
Add to your CI pipeline (GitHub Actions example):
- name: Run API tests
run: |
npm install -g @usebruno/cli
bru run ./api-tests --env ci
Importing from Postman
Bruno can import Postman collections:
- In Postman: Export your collection as Collection v2.1 JSON
- In Bruno: File → Import Collection → Postman
- Bruno converts the JSON to
.brufiles
The import handles most common Postman features: requests, folders, environments, and pre/post scripts (with minor syntax differences).
Team Workflow
The workflow that Bruno enables:
Individual workflow:
- Create a
api-tests/directory in your project - Add Bruno collections:
bruno.json+ request.brufiles - Commit to the repo
Team workflow:
- Developer adds a new API endpoint
- Opens Bruno, creates the request, writes tests
- Commits the
.brufiles alongside the API code in the same PR - Reviewers can see the API contract in the PR diff
- Merged — the test collection is the team's shared reference
This keeps API documentation and testing in sync with code automatically.
Limitations vs. Postman
Bruno's tradeoffs:
- No cloud sync — intentional; use git instead
- No mock servers — Postman's mock server feature doesn't exist in Bruno
- No team management UI — teams share via git, not a web dashboard
- No performance/load testing — use dedicated tools like k6 for load testing
- Desktop only — no web version
If your team needs cloud collaboration, Postman or Insomnia may still fit better. For teams already using git for everything, Bruno's model is a natural fit.
Why Developers Are Switching
The git-based workflow is the key draw. Treating API requests like code — versioned, reviewed, diffable — fits naturally into modern development practices.
The speed difference matters too: Bruno launches in under a second; Postman with its Electron runtime and cloud sync can take 5-10 seconds and consume significant memory.
For offline work (traveling, on a plane, in a location with spotty internet), Bruno works completely without a connection. Postman can behave unexpectedly when cloud sync fails.
Bruno is still maturing (1.x as of 2025) and some advanced Postman features are missing, but for typical REST API development, it covers everything most developers need — and the git workflow advantage is significant.
Subscribe to the DevTools Guide newsletter for weekly developer tool deep dives.