Introduction to Bruno API Client
Bruno is a revolutionary open-source API client that takes a fundamentally different approach to API testing and development. Unlike traditional cloud-based tools, Bruno stores collections directly on your filesystem using plain text markup language, making it truly Git-friendly and privacy-focused. This file-based architecture eliminates vendor lock-in and enables seamless collaboration through version control systems.
Launched in 2022, Bruno quickly gained traction among developers frustrated with subscription pricing models and cloud dependency. It’s particularly appealing for teams already using Git workflows and developers who value data sovereignty.
Why Bruno Stands Out
Core Philosophy and Advantages
Offline-First Architecture Bruno doesn’t require cloud synchronization or accounts. Everything runs locally:
- No login required
- Complete privacy
- Works without internet connection
- Fast and responsive
Git-Native Workflow
Collections are plain text files (.bru
format) that integrate seamlessly with Git:
- Version control built-in
- Easy code reviews for API changes
- Branch-based development
- Merge conflict resolution using standard Git tools
Open Source and Free Licensed under MIT, Bruno is truly free with no artificial limitations:
- No paid tiers or premium features
- Community-driven development
- Transparent roadmap
- Extensible architecture
Collections as Code
The .bru
file format is human-readable and designed for developers:
meta {
name: Get User
type: http
seq: 1
}
get {
url: {{base_url}}/api/users/{{user_id}}
}
headers {
Authorization: Bearer {{token}}
Content-Type: application/json
}
vars:pre-request {
user_id: 123
}
tests {
test("Status code is 200", function() {
expect(res.status).to.equal(200);
});
test("Response has user data", function() {
expect(res.body).to.have.property('id');
expect(res.body).to.have.property('name');
});
}
Getting Started with Bruno
Installation
Bruno is available for all major platforms:
# macOS (Homebrew)
brew install bruno
# Windows (Chocolatey)
choco install bruno
# Linux (Snap)
snap install bruno
# Or download from GitHub releases
# https://github.com/usebruno/bruno/releases
Creating Your First Collection
- Create New Collection: File → New Collection
- Choose Location: Select a folder in your Git repository
- Add Environment: Create environment files for different configurations
- Create Requests: Right-click collection → New Request
Directory Structure
my-api/
├── bruno.json # Collection metadata
├── environments/
│ ├── local.bru
│ ├── staging.bru
│ └── production.bru
└── requests/
├── auth/
│ ├── login.bru
│ └── refresh.bru
└── users/
├── get-user.bru
├── create-user.bru
└── update-user.bru
Understanding .bru File Format
The .bru
format is intuitive and expressive:
meta {
name: Create User
type: http
seq: 2
}
post {
url: {{base_url}}/api/users
}
headers {
Authorization: Bearer {{access_token}}
Content-Type: application/json
}
body:json {
{
"name": "{{user_name}}",
"email": "{{user_email}}",
"role": "developer"
}
}
vars:pre-request {
user_name: John Doe
user_email: john@example.com
}
script:pre-request {
// Generate timestamp
bru.setVar("timestamp", Date.now());
// Set custom header
req.setHeader("X-Request-ID", bru.getVar("request_id"));
}
script:post-response {
// Save user ID for subsequent requests
const userId = res.body.id;
bru.setEnvVar("created_user_id", userId);
// Log response time
console.log("Response time:", res.responseTime, "ms");
}
tests {
test("User created successfully", function() {
expect(res.status).to.equal(201);
expect(res.body.id).to.be.a('number');
});
}
Environment Management
Environment Files
Bruno environments are simple .bru
files:
local.bru
vars {
base_url: http://localhost:3000
api_key: local-dev-key
debug: true
}
production.bru
vars {
base_url: https://api.production.com
api_key: {{process.env.PROD_API_KEY}}
rate_limit: 1000
}
Secret Management
Bruno integrates with system environment variables for sensitive data:
vars {
database_url: {{process.env.DATABASE_URL}}
stripe_key: {{process.env.STRIPE_SECRET_KEY}}
}
This prevents secrets from being committed to Git while maintaining functionality.
Advanced Features
Scripting and Automation
Bruno supports JavaScript in pre-request and post-response scripts:
Pre-Request Script
// Generate HMAC signature
const crypto = require('crypto');
const message = req.getBody();
const secret = bru.getEnvVar('api_secret');
const signature = crypto
.createHmac('sha256', secret)
.update(message)
.digest('hex');
req.setHeader('X-Signature', signature);
Post-Response Script
// Extract and save pagination token
if (res.body.next_page_token) {
bru.setVar('page_token', res.body.next_page_token);
}
// Conditional logic
if (res.status === 401) {
// Token expired, trigger refresh
console.log('Refreshing authentication token...');
}
Built-in Testing Framework
Bruno includes Chai-style assertions:
tests {
// Status code assertions
test("Request successful", function() {
expect(res.status).to.be.oneOf([200, 201, 204]);
});
// Response time validation
test("Response time acceptable", function() {
expect(res.responseTime).to.be.below(500);
});
// JSON schema validation
test("Valid user object", function() {
expect(res.body).to.have.all.keys('id', 'name', 'email');
expect(res.body.id).to.be.a('number');
expect(res.body.email).to.match(/.+@.+\..+/);
});
// Header validation
test("Correct content type", function() {
expect(res.headers['content-type']).to.include('application/json');
});
}
Collections Collaboration
Git-Based Workflow
- Commit API changes alongside code changes
- Create pull requests for API modifications
- Review changes using standard Git diff tools
- Merge conflicts resolved with text editor
Example Git Workflow
# Create feature branch
git checkout -b feature/add-user-endpoints
# Make API changes in Bruno
# Commit changes
git add requests/users/
git commit -m "Add user CRUD endpoints"
# Push and create PR
git push origin feature/add-user-endpoints
Code Review of API Changes
+ post {
+ url: {{base_url}}/api/users
+ }
+
+ body:json {
+ {
+ "name": "{{user_name}}",
+ "email": "{{user_email}}"
+ }
+ }
Migration from Postman
Import Process
Bruno supports importing Postman collections. If you’re evaluating different API testing tools and comparing their features, Bruno’s import capability makes switching seamless:
- Export from Postman: Collection → Export → Collection v2.1
- Import to Bruno: Collection → Import → Select Postman JSON
- Review mapping: Check environment variables and scripts
- Adjust differences: Modify scripts if needed
Key Differences to Understand
Feature | Postman | Bruno |
---|---|---|
Storage | Cloud | Filesystem |
Format | JSON | .bru (text) |
Sync | Automatic | Git |
Pricing | Freemium | Free |
Collaboration | Built-in | Git-based |
Scripts | Postman sandbox | Node.js modules |
Workspaces | Cloud workspaces | Folders |
Script Migration
Postman Script
pm.environment.set("token", pm.response.json().access_token);
pm.test("Status is 200", () => {
pm.response.to.have.status(200);
});
Bruno Equivalent
bru.setEnvVar("token", res.body.access_token);
test("Status is 200", function() {
expect(res.status).to.equal(200);
});
CLI and Automation
Bruno CLI
Run collections from command line:
# Install CLI
npm install -g @usebruno/cli
# Run entire collection
bru run --collection ./my-api
# Run specific folder
bru run --collection ./my-api --folder users
# Use specific environment
bru run --collection ./my-api --env production
# Output formats
bru run --collection ./my-api --output junit.xml --format junit
CI/CD Integration
GitHub Actions Example
name: API Tests
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: '18'
- name: Install Bruno CLI
run: npm install -g @usebruno/cli
- name: Run API Tests
run: bru run --collection ./api-tests --env ci
env:
API_KEY: ${{ secrets.API_KEY }}
BASE_URL: ${{ secrets.BASE_URL }}
- name: Upload Results
uses: actions/upload-artifact@v3
with:
name: test-results
path: test-results.xml
GitLab CI Example
api-tests:
stage: test
image: node:18
script:
- npm install -g @usebruno/cli
- bru run --collection ./api-tests --env staging
variables:
API_KEY: $STAGING_API_KEY
artifacts:
reports:
junit: test-results.xml
GraphQL Support
Bruno handles GraphQL requests natively. For teams working with gRPC APIs, note that Bruno focuses primarily on REST and GraphQL:
meta {
name: Get User Posts
type: graphql
}
post {
url: {{graphql_endpoint}}
}
body:graphql {
query GetUserPosts($userId: ID!) {
user(id: $userId) {
id
name
posts(first: 10) {
edges {
node {
id
title
publishedAt
}
}
}
}
}
}
body:graphql:vars {
{
"userId": "{{user_id}}"
}
}
headers {
Authorization: Bearer {{graphql_token}}
}
Best Practices
Collection Organization
Logical Folder Structure
api-collection/
├── 01-authentication/
│ ├── login.bru
│ ├── logout.bru
│ └── refresh-token.bru
├── 02-users/
│ ├── list-users.bru
│ ├── get-user.bru
│ ├── create-user.bru
│ ├── update-user.bru
│ └── delete-user.bru
├── 03-posts/
└── 04-admin/
Naming Conventions
- Use descriptive names:
create-user.bru
notuser1.bru
- Prefix with numbers for ordering:
01-login.bru
,02-get-profile.bru
- Use kebab-case for consistency
Version Control
.gitignore Configuration
# Ignore local environment overrides
environments/local.bru
# Ignore temporary files
*.tmp
.bruno-cache/
# Keep tracked
!environments/*.example.bru
Meaningful Commit Messages
git commit -m "Add user registration endpoint with validation"
git commit -m "Update auth flow to use refresh tokens"
git commit -m "Fix pagination in list users endpoint"
Security
Environment Variables for Secrets
# Production environment
vars {
base_url: https://api.example.com
# Never hardcode secrets
api_key: {{process.env.PROD_API_KEY}}
db_password: {{process.env.DB_PASSWORD}}
}
Example .env File (git-ignored)
PROD_API_KEY=sk_live_xxxxxxxxxxxxx
DB_PASSWORD=super_secret_password
STRIPE_KEY=sk_live_xxxxxxxxxxxxx
Troubleshooting
Common Issues
Collections Not Loading
- Verify
bruno.json
exists in collection root - Check file permissions
- Ensure .bru files use correct syntax
Environment Variables Not Resolving
- Verify environment is selected
- Check variable name spelling
- For
process.env
variables, ensure they’re exported
Scripts Not Executing
- Check for JavaScript syntax errors
- Verify Node.js modules are available
- Review console output for error messages
Performance Optimization
Large Collections
- Split into multiple collections
- Use folder organization
- Selective loading in CLI
Response Time
- Monitor network conditions
- Check for timeout configurations
- Use connection pooling for bulk requests
Comparison: Bruno vs Competitors
When choosing an API client, it’s worth comparing Bruno against alternatives like Insomnia REST client:
Feature | Bruno | Postman | Insomnia | Thunder Client |
---|---|---|---|---|
Cost | Free | Freemium | Freemium | Free + Pro |
Offline | Yes | Limited | Yes | Yes |
Storage | Local files | Cloud | Hybrid | VS Code workspace |
Git-friendly | Excellent | Poor | Moderate | Good |
Collaboration | Git-based | Built-in | Paid | Limited |
Learning Curve | Low | Medium | Low | Very Low |
Extensibility | Scripts | Scripts + Apps | Plugins | Limited |
Conclusion
Bruno API Client represents a paradigm shift in API testing tools, prioritizing developer workflows, data ownership, and simplicity. Its file-based architecture aligns perfectly with modern Git-centric development practices, making it an ideal choice for teams already comfortable with version control.
The open-source nature ensures transparency, community-driven innovation, and freedom from vendor lock-in. While it may lack some advanced features found in established tools like Postman, Bruno’s focus on core functionality and developer experience makes it increasingly attractive for teams seeking a lightweight, privacy-focused alternative.
Whether you’re migrating from Postman, starting fresh with API testing, or looking for better integration with your Git workflow, Bruno offers a compelling, cost-effective solution that puts developers first.