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

  1. Create New Collection: File → New Collection
  2. Choose Location: Select a folder in your Git repository
  3. Add Environment: Create environment files for different configurations
  4. 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

  1. Commit API changes alongside code changes
  2. Create pull requests for API modifications
  3. Review changes using standard Git diff tools
  4. 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:

  1. Export from Postman: Collection → Export → Collection v2.1
  2. Import to Bruno: Collection → Import → Select Postman JSON
  3. Review mapping: Check environment variables and scripts
  4. Adjust differences: Modify scripts if needed

Key Differences to Understand

FeaturePostmanBruno
StorageCloudFilesystem
FormatJSON.bru (text)
SyncAutomaticGit
PricingFreemiumFree
CollaborationBuilt-inGit-based
ScriptsPostman sandboxNode.js modules
WorkspacesCloud workspacesFolders

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 not user1.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:

FeatureBrunoPostmanInsomniaThunder Client
CostFreeFreemiumFreemiumFree + Pro
OfflineYesLimitedYesYes
StorageLocal filesCloudHybridVS Code workspace
Git-friendlyExcellentPoorModerateGood
CollaborationGit-basedBuilt-inPaidLimited
Learning CurveLowMediumLowVery Low
ExtensibilityScriptsScripts + AppsPluginsLimited

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.