Bruno: Open Source Postman Alternative for APIs
When it comes to API development, testing, and documentation, Postman has long been the go‑to tool for many developers. However, its proprietary nature, hefty memory footprint, and occasional licensing constraints push teams to explore open‑source alternatives. Enter Bruno – a lightweight, community‑driven platform that offers a Postman‑like experience right from your terminal and browser.
Bruno’s claim to fame is its focus on simplicity, speed, and extensibility. Built with modern web technologies, it provides a clean UI for designing collections while also delivering a powerful CLI for automation. In this article, we’ll dive deep into Bruno’s core features, walk through real‑world use cases, and share pro tips to help you get the most out of this emerging tool.
Getting Started: Installation & Setup
Bruno is available as a desktop app (Electron‑based) and a CLI package. The desktop version works on Windows, macOS, and Linux, while the CLI can be installed via npm, Homebrew, or a direct binary download.
Desktop Installation
- Visit the official GitHub releases page.
- Download the installer matching your OS (e.g.,
Bruno-Setup.exefor Windows). - Run the installer and follow the on‑screen prompts.
Once installed, launch Bruno and you’ll be greeted with a clean workspace where you can create collections, environments, and request folders.
CLI Installation
# Using npm
npm install -g @usebruno/cli
# Using Homebrew (macOS)
brew tap usebruno/bruno
brew install bruno
Verify the installation:
bruno --version
# Expected output: v1.x.x
Creating Your First Collection
A collection in Bruno is a JSON file that groups related API requests. You can create one via the UI or directly in the file system. Let’s start with the UI approach.
- Click New Collection → give it a name, e.g., Todo API.
- Add a folder named Tasks to organize CRUD endpoints.
- Within Tasks, create a GET request to
https://jsonplaceholder.typicode.com/todos.
Save the collection; Bruno stores it as todo-api.bru in your workspace directory. You can now run the request from the UI or invoke it via the CLI.
Running the Request from CLI
bruno run todo-api.bru --request "GET /todos"
The output includes the status code, response time, and a formatted JSON body, making debugging a breeze.
Advanced Request Configuration
Beyond simple GET calls, Bruno supports headers, query parameters, authentication, and even pre‑request scripts written in JavaScript. These features let you emulate complex production scenarios.
Adding Authentication
- Open the request editor.
- Navigate to the Auth tab.
- Select Bearer Token and paste your JWT.
Alternatively, you can store the token in an environment variable and reference it with {{access_token}}. This promotes reusability across multiple requests.
Pre‑request Script Example
Suppose you need to generate a timestamp header for every request. Add the following JavaScript snippet in the Pre‑request Script section:
const now = new Date().toISOString();
pm.request.headers.add({ key: 'X-Timestamp', value: now });
When the request runs, Bruno injects the header automatically, ensuring each call carries a fresh timestamp.
Pro Tip: Use environment variables for dynamic values like{{base_url}}or{{api_key}}. This keeps your collection portable across dev, staging, and prod environments without manual edits.
Testing & Assertions
Testing is where Bruno shines. Its built‑in test runner lets you write assertions in JavaScript, similar to Postman’s pm.test API. Tests are executed after each request, and failures are clearly reported in the CLI output.
Simple Response Validation
Let’s verify that the /todos endpoint returns an array with at least one element:
pm.test("Response is an array", () => {
const json = pm.response.json();
pm.expect(Array.isArray(json)).to.be.true;
});
pm.test("Array has items", () => {
const json = pm.response.json();
pm.expect(json.length).to.be.greaterThan(0);
});
Save the script under the Tests tab. Running the request now yields a pass/fail summary.
Chaining Requests with Dynamic Data
Often you need to create a resource, capture its ID, and use that ID in a subsequent request. Bruno’s pm.variables.set method makes this straightforward.
// POST /tasks to create a new task
pm.test("Task created", () => {
pm.response.to.have.status(201);
const body = pm.response.json();
pm.variables.set("newTaskId", body.id);
});
In the next request (e.g., GET /tasks/{{newTaskId}}), the variable {{newTaskId}} is automatically substituted.
Integrating Bruno into CI/CD Pipelines
Automation is key for modern development workflows. Bruno’s CLI can be embedded in GitHub Actions, GitLab CI, or any Jenkins pipeline to run API sanity checks on every commit.
GitHub Actions Example
name: API Smoke Test
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Install Node.js
uses: actions/setup-node@v3
with:
node-version: '20'
- name: Install Bruno CLI
run: npm install -g @usebruno/cli
- name: Run Collection
run: |
bruno run collections/todo-api.bru --environment dev
This workflow checks out the repository, installs the CLI, and executes the collection against the dev environment. Failed assertions will cause the job to fail, alerting the team immediately.
Parallel Execution for Large Suites
When you have dozens of collections, you can speed up execution by running them in parallel using GNU parallel or a custom Node script. Here’s a concise Node snippet:
const { exec } = require('child_process');
const collections = ['users.bru', 'orders.bru', 'inventory.bru'];
collections.forEach(col => {
exec(`bruno run ${col} --environment prod`, (err, stdout, stderr) => {
if (err) {
console.error(`❌ ${col} failed`);
console.error(stderr);
} else {
console.log(`✅ ${col} passed`);
console.log(stdout);
}
});
});
Integrate this script into your CI step to achieve concurrent runs without additional tooling.
Real‑World Use Cases
Below are three scenarios where teams have successfully replaced Postman with Bruno.
Microservice Contract Testing
- Team A maintains separate repositories for each microservice.
- Each repo contains a Bruno collection that defines the service’s public contract (endpoints, schemas, auth).
- During CI, the collection runs against a mock server, ensuring backward compatibility before merging.
Performance Monitoring
Bruno’s CLI can output response times in milliseconds. By piping the output to grep and awk, you can generate simple performance dashboards.
bruno run load-test.bru | grep "Response Time" | awk '{print $4}' > times.txt
The resulting times.txt can be fed into Grafana or a custom charting tool.
Documentation Generation
Bruno can export collections to OpenAPI (Swagger) specifications, which can then be rendered as interactive docs.
bruno export todo-api.bru --format openapi > openapi.yaml
Feed openapi.yaml into tools like Redoc or Swagger UI for a polished developer portal.
Pro Tip: Keep a dedicated docs/ folder in your repo for exported OpenAPI files. Automate the export step in CI to ensure docs are always in sync with the latest collection.
Extending Bruno with Plugins
While Bruno ships with a robust core, its plugin architecture allows developers to add custom functionality. Plugins are simple Node modules that expose a register function.
Sample Plugin: Random Data Generator
This plugin adds a {{random_email}} variable that returns a unique email address each time it’s called.
// random-email-plugin/index.js
module.exports.register = (bruno) => {
bruno.addVariable('random_email', () => {
const chars = 'abcdefghijklmnopqrstuvwxyz';
const name = Array.from({length: 8}, () => chars[Math.floor(Math.random()*chars.length)]).join('');
return `${name}@example.com`;
});
};
Install the plugin locally:
npm install ./random-email-plugin
Then reference the variable in any request body:
{
"email": "{{random_email}}",
"name": "John Doe"
}
Whenever the request runs, Bruno injects a fresh email, perfect for testing sign‑up flows without manual data entry.
Community, Support, and Roadmap
Bruno is actively maintained by a vibrant open‑source community. The GitHub repository boasts over 12 k stars, frequent releases, and a lively Discussions board where users share patterns and troubleshoot issues.
- Documentation: docs.usebruno.com – comprehensive guides, API reference, and migration tips.
- Slack Channel: Join the
#bruno-devworkspace for real‑time help. - Roadmap Highlights: Native support for GraphQL, enhanced test reporting, and a visual flow editor slated for Q4 2026.
If you encounter bugs or have feature ideas, opening an issue on GitHub is the fastest way to get attention. The maintainers prioritize community‑driven enhancements, making Bruno a truly collaborative project.
Conclusion
Bruno offers a compelling, open‑source alternative to Postman, combining a sleek UI with a powerful CLI that fits seamlessly into modern DevOps pipelines. Its lightweight footprint, extensibility through plugins, and strong community backing make it a solid choice for teams seeking flexibility without sacrificing functionality.
Whether you’re building microservice contracts, automating smoke tests, or generating up‑to‑date API documentation, Bruno equips you with the tools to do it efficiently. Give it a spin, contribute to its ecosystem, and you’ll discover a new rhythm for API development that aligns perfectly with the open‑source ethos of Codeyaan.