Bruno API Collections in Git: A Workflow Shift, Not Free Postman
Committing Bruno .bru files to the repo keeps the API contract in the same PR and history as the code. The only real tax is a deliberate secrets boundary.
Your API collection lives in a vendor cloud or on one engineer’s laptop, while the code that serves those endpoints lives in git. The two drift apart the moment an endpoint adds a required header or renames a field: the code change ships in a pull request, but the saved request stays behind, and the next person who opens the collection debugs a request that no longer matches reality. The fix is to commit your request collection as plain-text files next to the code, so the API contract travels through the same pull request, the same review, and the same history as the handler it exercises. The tool here is Bruno, but the argument is about where the contract lives rather than which client renders it; there is a firm boundary around secrets, and a few cases where cloud collaboration still wins.
Swapping one client for another that happens to be free changes the tooling bill; committing the collection changes the workflow.
Bru Files in the Diff#
Bruno stores each request as a plain-text file in a collection folder you check into the repo. The format is “Bru,” a simple markup language built on plain-text files; it is a small DSL, not JSON, which is what makes the diffs read cleanly in a pull request. A minimal request looks like this:
meta {
name: Get user
type: http
seq: 1
}
get {
url: {{baseUrl}}/users/42
body: none
auth: bearer
}
headers {
Accept: application/json
}
auth:bearer {
token: {{authToken}}
}
assert {
res.status: eq 200
res.body.id: eq 42
}
When the /users/42 endpoint starts requiring a new Accept value, or the response gains a field an assertion should check, you edit this file in the same commit as the handler. The reviewer sees both diffs side by side: the handler change on one side, the request change on the other.
Contrast that with a cloud-hosted collection. The handler edit lives in git; the request edit lives in the vendor app. That is two systems and two timelines, and drift is the default. Bruno’s own framing matches this directly: it positions itself as “the Git-native API client” with “collections stored as code,” and it is explicit that it is “Local Only” with no cloud sync, so your data stays in the repo. Collaboration happens through pull requests, because the human-readable file format makes the change easy to read.
One note on the format, because the tool is moving. Bruno still fully supports .bru, but it now recommends OpenCollection YAML, an open spec it authored, for new collections. The argument does not depend on the file extension. Both formats are plain text, both live in the repo, and both review through a pull request. Lead with .bru if that is what your existing collections use; reach for OpenCollection YAML on a greenfield collection.
The Secrets Boundary#
You do not commit everything. The request shape goes in git; the secret values do not. Bruno’s docs are blunt about this, stating that whether you check the collection into git or export it, “we want to ensure that the secrets are stripped out of the collection before it is shared.”
The cleanest default is the secret variable. In an environment, you tick the secret checkbox on a variable. Bruno then manages that value internally and never writes it into the environment file; the file records only the variable name. The shape that lands in git is the name and nothing else:
vars:secret [
baseUrl
]
The value lives on your local machine, encrypted with OS-level encryption when available and falling back to AES256. Bruno also strips secret variables when you export a collection.
The second path is the one most backend engineers already know: a .env file at the root of the collection folder, read inside Bruno through process.env. The documented folder structure ships a .gitignore alongside the .env, so the pattern is the familiar one. The .env stays git-ignored, while the request files and bruno.json are committed. If your application already handles secrets this way, the collection follows the same rule, and a new teammate populates one local file. For org-scale setups, Bruno’s Ultimate tier integrates with HashiCorp Vault, AWS Secrets Manager, and Azure Key Vault, so values resolve from a vault without touching disk; that is the paid option, off the default path, and most teams never need it.
Two failure modes show up when teams skip this discipline. A real token pasted into a plain vars block instead of vars:secret, then pushed to a public repo, lands in history and stays there; tick the secret checkbox so the value never reaches the file, or move it to the git-ignored .env. The subtler one is treating the collection as an export you regenerate occasionally. An exported-then-committed JSON dump is drift waiting to happen, because nobody edits it in the same PR as the handler. The request file is a source file, edited by hand alongside the code. Document the boundary in the collection’s README so the next teammate knows which file to populate locally and which never to touch.
Where a Cloud Workspace Still Wins#
Git fluency is the price of admission. A non-technical QA tester, or a stakeholder who needs a click-to-run shared workspace, is genuinely better served by a cloud tool; if a meaningful share of your request-runners lack git, the flat-file model adds friction instead of removing it. Sharing across teams with no common repo has the same shape. “Clone our repo” is a fine onboarding step inside one codebase and falls apart as a discovery mechanism across an organization, where a cloud workspace with org-wide search wins.
The other limit is capability. A local-first flat-file client ships no mock servers, monitors, scheduled health checks, hosted dashboards, or performance and load testing. Bruno is explicitly local-only with no cloud sync, so for those you stay on a cloud platform or add separate tooling. Postman, for instance, provides mock servers, monitors, and performance testing as cloud features.
Postman is account-centric and cloud-sync-by-default: collections live in a Postman account and workspace, and sync to the cloud unless you deliberately avoid it. Bruno’s local-first model keeps them in your repo next to the code.
Before the First Push#
For an engineering team where the people running the requests already live in git, the flat-file collection is the default; treat it as part of the codebase. Where that assumption breaks, the diagram above sends you back to a cloud workspace. The one piece of discipline the workflow demands is the secrets boundary, so set up the secret variable checkbox or a git-ignored .env before the first push, and document which file each teammate fills in locally.
References#
- Bru Markup Language - Bruno Docs (opens in new tab) - Confirms
.bruis a plain-text DSL for version-controlled collections; notes OpenCollection YAML is now the recommended format for new collections. - OpenCollection YAML - Bruno Docs (opens in new tab) - The YAML spec Bruno now recommends for new collections; relevant for the format-shift caveat.
- Secret Management overview - Bruno Docs (opens in new tab) - States secrets must be stripped before checking-in or exporting; lists the three secret approaches.
- Secret Variables - Bruno Docs (opens in new tab) - The
secretcheckbox; values are not written to the env file (vars:secret [...]), stored OS-encrypted or AES256. The default safe-to-commit pattern. - DotEnv File - Bruno Docs (opens in new tab) - A
.envat the collection root plus.gitignore, accessed viaprocess.env; the familiar backend pattern. - Secret Managers overview - Bruno Docs (opens in new tab) - HashiCorp Vault, AWS Secrets Manager, and Azure Key Vault integration, gated to Bruno Ultimate.
- Bruno (GitHub repository) (opens in new tab) - The
@usebruno/cliandbru runfor CI, and the language breakdown showing a dedicated “Bru” language. - Bruno LICENSE (opens in new tab) - MIT License, Copyright (c) 2022 Anoop M D, Anusree P S and Contributors. Confirms the core is MIT rather than source-available or commercial.
- Bruno latest release (GitHub) (opens in new tab) - The latest published release tag and its changelog.
- Bruno Pricing (opens in new tab) - Open Source, Pro, and Ultimate tiers per user per month, billed annually. The git-in-repo workflow needs only the free core.
- Bruno homepage (opens in new tab) - “The Git-native API client,” “Local Only,” “Collections stored as code.” The primary source for Bruno’s own positioning.
- About Postman accounts - Postman Docs (opens in new tab) - Postman’s account-centric, cloud-sync-by-default model; the contrast point.
- Install Postman - Postman Docs (opens in new tab) - The web versus desktop feature split; supports the cloud-only-features point (mock servers, monitors, performance testing).
Related posts
A practical guide to consumer-driven contract testing with Pact in TypeScript microservices, catching breaking API changes before deployment.
testing · microservices · api-design +3
Rushing feels fast but creates rework, bugs, and firefighting. Why pausing for refactoring, tests, and CI upkeep is an investment in speed, not lost speed.
technical-debt · testing · ci-cd +2
A mobile binary can't be rolled back and old versions linger, so safety and speed move server-side: a BFF, consumer-driven contracts, and backward-compatible versioning.
mobile · api-design · testing +1
A practical guide to API versioning: URL vs header approaches, breaking changes, Sunset-header deprecation, AWS API Gateway, GraphQL, and contract testing.
api-design · deployment · graphql +4
A production guide to feature flags in distributed systems, comparing LaunchDarkly, Unleash, and AWS AppConfig with examples for rollouts and A/B testing.
feature-flags · devops · ci-cd +5