DEV Community

Cover image for Bruno: The Git-Native API Client Developers Should Try
Moniruzzaman Saikat
Moniruzzaman Saikat

Posted on

Bruno: The Git-Native API Client Developers Should Try

Modern API development requires more than a tool that can send HTTP requests.

Developers need API collections that can be reviewed, versioned, tested, shared, and executed inside CI pipelines. However, many API clients treat collections as data stored inside their own platform.

Bruno takes a different approach.

Bruno is an open-source, local-first, and Git-native API client. Instead of hiding API collections inside a cloud workspace, Bruno stores them as readable files on your filesystem.

That simple decision makes Bruno fit naturally into a developer's existing workflow.

What Is Bruno?

Bruno is a desktop API client for developing and testing APIs.

You can use it to work with:

  • REST APIs
  • GraphQL APIs
  • Authentication flows
  • Environment variables
  • Request scripts
  • Response tests
  • API documentation
  • Automated collection runs

It covers many workflows developers already use tools like Postman or Insomnia for.

The major difference is how Bruno stores and manages API collections.

Your requests live inside regular folders and text files. You can open them in an editor, commit them to Git, review changes in pull requests, and keep them beside your application code.

Why Git-Native API Collections Matter

Imagine that your backend repository contains the following structure:

my-api/
├── app/
├── database/
├── routes/
├── tests/
└── api-collection/
    ├── users/
    ├── authentication/
    ├── products/
    └── bruno.json
Enter fullscreen mode Exit fullscreen mode

The API collection can be committed to the same repository as the application.

When a developer changes an endpoint, they can update the related Bruno request in the same branch.

A pull request can then contain:

Updated user registration validation
Updated registration API request
Added registration error tests
Updated API documentation
Enter fullscreen mode Exit fullscreen mode

The backend implementation and its API examples remain synchronized.

This is much cleaner than updating code in Git while separately maintaining a collection inside an external cloud workspace.

Collections Are Readable Files

Bruno stores requests as plain-text files.

A simplified Bruno request may look similar to this:

meta {
  name: Get Current User
  type: http
  seq: 1
}

get {
  url: {{baseUrl}}/api/user
  body: none
  auth: bearer
}

auth:bearer {
  token: {{accessToken}}
}

tests {
  test("should return the authenticated user", function () {
    expect(res.status).to.equal(200);
  });
}
Enter fullscreen mode Exit fullscreen mode

Because the request is text-based, it works well with:

  • Git diffs
  • Pull requests
  • Code reviews
  • Merge history
  • IDE search
  • Automated agents
  • Repository backups

You do not need a proprietary interface just to understand what changed.

Local-First by Design

Bruno runs locally and does not require an account for its normal open-source workflow.

Your requests, environments, scripts, and collections stay on your machine or inside repositories you control.

This is useful when working with:

  • Private APIs
  • Internal company services
  • Sensitive authentication flows
  • Client projects
  • Local development environments
  • Restricted enterprise networks

A local-first tool also avoids making your API workflow dependent on the availability of an external cloud platform.

Creating Your First Bruno Collection

After installing Bruno, create a new collection and select a directory on your filesystem.

For example:

projects/
└── inventory-api/
    └── bruno/
Enter fullscreen mode Exit fullscreen mode

Create an environment containing variables such as:

vars {
  baseUrl: http://localhost:8000
  accessToken:
}
Enter fullscreen mode Exit fullscreen mode

You can then create a login request:

POST {{baseUrl}}/api/login
Enter fullscreen mode Exit fullscreen mode

With a JSON body:

{
  "email": "developer@example.com",
  "password": "password"
}
Enter fullscreen mode Exit fullscreen mode

After receiving the response, you can save the token using a post-response script:

if (res.status === 200) {
  bru.setEnvVar("accessToken", res.body.token);
}
Enter fullscreen mode Exit fullscreen mode

Other authenticated requests can reuse the value:

Authorization: Bearer {{accessToken}}
Enter fullscreen mode Exit fullscreen mode

This creates a repeatable authentication workflow without manually copying tokens after every login.

Testing API Responses

Bruno supports response tests, allowing a collection to work as both documentation and executable validation.

For example:

test("status should be 200", function () {
  expect(res.status).to.equal(200);
});

test("response should contain a user", function () {
  expect(res.body).to.have.property("user");
});

test("user should have an email", function () {
  expect(res.body.user).to.have.property("email");
});
Enter fullscreen mode Exit fullscreen mode

You can also validate response types:

test("products should be an array", function () {
  expect(res.body.products).to.be.an("array");
});
Enter fullscreen mode Exit fullscreen mode

Or check business rules:

test("product stock should not be negative", function () {
  res.body.products.forEach(function (product) {
    expect(product.stock).to.be.at.least(0);
  });
});
Enter fullscreen mode Exit fullscreen mode

These tests help catch API regressions before they reach production.

Environments and Variables

Most applications have multiple environments:

  • Local
  • Development
  • Staging
  • Production

Instead of duplicating requests, you can create environment-specific variables.

Local environment:

vars {
  baseUrl: http://localhost:8000
}
Enter fullscreen mode Exit fullscreen mode

Staging environment:

vars {
  baseUrl: https://staging-api.example.com
}
Enter fullscreen mode Exit fullscreen mode

Production environment:

vars {
  baseUrl: https://api.example.com
}
Enter fullscreen mode Exit fullscreen mode

Your request remains unchanged:

GET {{baseUrl}}/api/products
Enter fullscreen mode Exit fullscreen mode

You only switch the active environment.

Sensitive values such as API keys and access tokens should be stored separately from committed collection files.

Bruno automatically creates recommended Git ignore rules when creating collections, but developers should still review files before committing them.

Collaboration Through Git

A Bruno collection can be managed like any other source code directory.

Initialize Git:

git init
Enter fullscreen mode Exit fullscreen mode

Add the collection:

git add .
Enter fullscreen mode Exit fullscreen mode

Create a commit:

git commit -m "Add authentication API collection"
Enter fullscreen mode Exit fullscreen mode

Connect it to a remote repository:

git remote add origin https://github.com/username/project-api.git
git push -u origin main
Enter fullscreen mode Exit fullscreen mode

Team members can clone the repository and open the same collection in Bruno.

Changes can be reviewed using normal Git commands:

git diff
git status
git log
Enter fullscreen mode Exit fullscreen mode

This gives API collections the same review process as application code.

Running Collections From the CLI

Bruno provides a command-line interface called Bruno CLI.

Install it using npm:

npm install -g @usebruno/cli
Enter fullscreen mode Exit fullscreen mode

Navigate to the collection directory:

cd path/to/bruno-collection
Enter fullscreen mode Exit fullscreen mode

Run the entire collection:

bru run
Enter fullscreen mode Exit fullscreen mode

Run it with a selected environment:

bru run --env local
Enter fullscreen mode Exit fullscreen mode

Run only a specific folder:

bru run users
Enter fullscreen mode Exit fullscreen mode

You can also pass environment variables directly:

bru run --env local --env-var ACCESS_TOKEN=your-token
Enter fullscreen mode Exit fullscreen mode

To run requests with selected tags:

bru run --tags=smoke,sanity
Enter fullscreen mode Exit fullscreen mode

To exclude unfinished or skipped requests:

bru run --exclude-tags=skip,draft
Enter fullscreen mode Exit fullscreen mode

This makes Bruno useful beyond manual API exploration.

Your collection can become part of your automated testing system.

Using Bruno in CI/CD

A simple CI workflow can install Bruno CLI and run an API collection after starting the application.

A simplified GitHub Actions example could look like this:

name: API Tests

on:
  push:
    branches:
      - main
  pull_request:

jobs:
  api-tests:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout repository
        uses: actions/checkout@v4

      - name: Set up Node.js
        uses: actions/setup-node@v4
        with:
          node-version: 22

      - name: Install Bruno CLI
        run: npm install -g @usebruno/cli

      - name: Run API tests
        working-directory: ./bruno
        run: bru run --env ci
Enter fullscreen mode Exit fullscreen mode

For a real application, you would also start the backend service and database before executing the collection.

For example:

- name: Install backend dependencies
  run: composer install --no-interaction

- name: Prepare environment
  run: |
    cp .env.example .env
    php artisan key:generate
    php artisan migrate --force

- name: Start Laravel server
  run: php artisan serve --host=127.0.0.1 --port=8000 &

- name: Run Bruno tests
  working-directory: ./bruno
  run: bru run --env ci
Enter fullscreen mode Exit fullscreen mode

Now every pull request can verify that important API endpoints still work.

Generating Test Reports

Bruno CLI supports report formats that can be used in automated pipelines.

Depending on your workflow, you can generate formats such as:

  • JSON
  • JUnit
  • HTML

JUnit output is especially useful because many CI platforms can display test results directly.

A collection can therefore serve several purposes:

  1. Manual API exploration
  2. Developer documentation
  3. Regression testing
  4. CI validation
  5. Integration testing

This reduces the need to maintain separate API examples and testing assets.

Importing Existing Collections

Teams do not need to rebuild every request manually before trying Bruno.

Bruno supports importing collections from common formats and tools, including Postman collections.

A practical migration process is:

  1. Export the existing collection.
  2. Import it into Bruno.
  3. Verify environment variables and authentication.
  4. Review scripts and tests.
  5. Store the new collection inside the project repository.
  6. Commit it to Git.
  7. Add CLI execution to CI.

Some tool-specific scripts may require adjustment, but importing provides a useful starting point.

API Documentation Inside the Collection

Bruno supports Markdown documentation at different levels, including:

  • Workspace
  • Collection
  • Folder
  • Request

You can document an authentication folder with information such as:

# Authentication

All protected endpoints require a bearer token.

## Login

Send the user's email and password to `/api/login`.

The response contains an access token that should be included in the
`Authorization` header for future requests.
Enter fullscreen mode Exit fullscreen mode

Documentation stays close to the executable requests.

When an endpoint changes, developers can update the request, tests, and explanation together.

Bruno can also generate standalone HTML documentation from collections, making it possible to publish readable API references without maintaining a completely separate source.

Bruno 4 and AI Assistance

Bruno 4.0 introduced Bring Your Own Key AI capabilities.

Instead of forcing developers to use a fixed AI provider, Bruno supports OpenAI-compatible providers and custom model configurations.

Its AI features can assist with:

  • Writing request documentation
  • Generating scripts
  • Creating response tests
  • Reviewing changes
  • Working with the active request or collection
  • Providing scripting autocomplete

The Bring Your Own Key approach gives developers more control over the provider, model, and credentials they use.

Bruno also includes security controls that explain what context is shared with the selected AI provider.

AI assistance is most useful here when it improves the files developers already own instead of moving their entire API workflow into another closed platform.

Where Bruno Fits Best

Bruno is particularly suitable for teams that:

  • Already use Git for collaboration
  • Want collections stored beside application code
  • Prefer local-first development tools
  • Need readable API request files
  • Want API tests inside CI pipelines
  • Work with private or internal APIs
  • Want to avoid mandatory cloud accounts
  • Need an open-source API client
  • Use coding agents or IDE-based workflows

It is also a strong option for solo developers because collections can be backed up and shared through any Git repository.

Things to Consider

Bruno's workflow may feel different if your team is accustomed to browser-based collaboration.

Git-based collaboration assumes that team members understand concepts such as:

  • Repositories
  • Commits
  • Branches
  • Pull requests
  • Merge conflicts

This is usually not a major issue for engineering teams, but it can require onboarding for non-technical users.

Some advanced Git interface features and team capabilities are also associated with paid Bruno editions. You should compare the current editions before standardizing it across a larger organization.

The core open-source workflow, however, remains highly useful for individual developers and technical teams.

Bruno vs Traditional Cloud-Based API Clients

The main difference is not the request editor.

Most established API clients can send requests, manage environments, and execute tests.

The important difference is ownership and workflow.

A traditional cloud-based workflow often looks like this:

Application code -> Git repository
API collection -> External platform
Documentation -> Another platform
Automated tests -> Separate test suite
Enter fullscreen mode Exit fullscreen mode

A Bruno-based workflow can look like this:

Application code -> Git repository
API collection -> Same Git repository
API documentation -> Same collection
API tests -> Same collection
CI execution -> Bruno CLI
Enter fullscreen mode Exit fullscreen mode

This does not eliminate every tool, but it reduces fragmentation.

A Practical Project Structure

For a Laravel API, I would organize the project like this:

inventory-platform/
├── app/
├── database/
├── routes/
├── tests/
├── bruno/
│   ├── authentication/
│   │   ├── login.bru
│   │   ├── logout.bru
│   │   └── current-user.bru
│   ├── products/
│   │   ├── list-products.bru
│   │   ├── create-product.bru
│   │   └── update-product.bru
│   ├── orders/
│   │   ├── create-order.bru
│   │   └── get-order.bru
│   ├── environments/
│   ├── collection.bru
│   └── bruno.json
├── composer.json
└── README.md
Enter fullscreen mode Exit fullscreen mode

Each backend feature should include:

  • Its implementation
  • Automated application tests
  • Bruno requests
  • Bruno response tests
  • Relevant API documentation

A feature is not complete until its executable API examples are updated.

Conclusion

Bruno is not interesting simply because it is another API client.

It is interesting because it treats API collections as part of the codebase.

Collections can be stored locally, reviewed through Git, executed through the command line, validated in CI, and edited with normal developer tools.

That makes the API workflow easier to own and maintain.

For developers who value local tools, open-source software, readable files, and Git-based collaboration, Bruno is worth testing on a real project.

You can explore it at usebruno.com and keep your first collection inside your next API repository.

Top comments (0)