BLOG
Bruno入門|PostmanからBrunoへ移行するAPIクライアント管理術

API クライアント技術における革新。

bruno クライアントロゴ
ブロートウェアにさようなら、シンプルさ、効率性、自由を手に入れましょう。
Postman の重いソフトウェアにうんざりしていませんか?オープンソースの革命に参加しましょう!✊
ミッション
-
独自の扱いにくいインターフェース 👎 にさよならを告げ、API 開発者のためのスマートなオープンソースの世界へようこそ 👍
-
API コレクションをコードと同じ場所に配置(醜い JSON ブロブはもう不要です!)💻
-
すべてを簡単にバージョン管理。肥大化したワークスペースは不要!🎮
-
リポジトリをクローンし、Bruno を起動して、すぐに API を操作できます 🤾
-
コレクションの紛失の心配はもうありません!✨
Bruno とは
Bruno は API クライアント技術における革新で、「Tim のコレクションが見つからない」という問題に別れを告げます:「混乱した引き継ぎはもう終わり」。Bruno では、API クライアントファイルがプロジェクトと同じディレクトリに存在するため、常に全員が同じページを共有できます。開発者のために、シンプルさ、効率性、自由を重視しています。
上記の説明は少し分かりにくいかもしれません。もっと簡単に説明しましょう。例えば、ABC 会社のプロジェクト XYZ で、Postman、Insomnia、Hoppscotch などの API クライアントを使用して API を開発するとします。プロジェクト XYZ が完了し、担当の開発者が既に退職した場合、次のような問題が発生します:
-
開発者がドキュメントを作成していなかった場合はどうなりますか?
-
開発者が API クライアント(Postman、Insomnia、Hoppscotch)のコレクションを保存していなかった場合はどうなりますか?
-
何らかの理由で Postman、Insomnia、Hoppscotch のクラウドにアクセスできない場合はどうなりますか?
ここで、開発者が API を作成し、API クライアントのコレクションをコードが存在する同じリポジトリに保存する場合を想像してみてください。これこそが Bruno が提供するものです。これは API のテストとバージョン管理システムを使用したプロジェクト管理の新しい方法です。素晴らしいと思いませんか?
Brunoは独自のDSL(ドメイン特化言語)を持っています。新しい言語を学ぶのか、と気が重くなるかもしれませんが、ご心配なく。実際にはJSONやYAMLよりも簡単に習得できます。なぜ開発者が新しい言語を作ることを選んだのか興味がある方は、以下のディスカッションをご覧ください:
なぜドメイン特化言語なのか?
Bru言語について
まずはBrunoの言語「Bru」について理解しましょう。その構文はGroovy言語に似ています。主にブロックとタグで構成されています。
1. ブロック
Bruファイルは以下の3種類のブロックで構成されています:
-
辞書ブロック:キーと値のペアの集合です。
-
テキストブロック:複数行のテキストで構成されます。
-
配列ブロック:文字列のリストです。
1. 辞書ブロック
get { url: <https://api.textlocal.in/send> } headers { content-type: application/json Authorization: Bearer 123 ~transaction-id: {{transactionId}} }
辞書ブロック内のキーの前に~を付けることで、そのキーを無効化できます。
2. テキストブロック
テキストブロックは複数行のテキストで構成されます。
tests { expect(res.status).to.equal(200); }
3. 配列ブロック
vars:secret [ access_key, access_secret, ~transactionId ]
詳細については以下をご参照ください:言語設計
2. タグ
Bruにはタグという機能があります。これはBrunoに特定の意味を持たせるものです。主なタグには以下のようなものがあります:
1. meta:リクエストに関するメタデータを格納します。
meta { name: Get users, type: http seq: 1 }
type: can be either http or graphql. seq: is used to store the sequence number. This decides the sort position of your request in the UI. [which request should run first.]
2. get:GETのHTTPリクエストを実行します。
get { url: <https://api.github.com/users/usebruno> }
3. post:POSTのHTTPリクエストを実行します。
post { url: <https://api.github.com/users/usebruno> }
4. put:PUTのHTTPリクエストを実行します。
put { url: <https://api.github.com/users/usebruno> }
5. delete:DELETEのHTTPリクエストを実行します。
delete { url: <https://api.github.com/users/usebruno> }
詳細については以下をご参照ください:Bruタグリファレンス
Bru言語のシンタックスハイライトサポートについては、シンタックスハイライトサポートをご覧ください。
また、シークレット管理、スクリプティング、テストについてもご一読ください。
スクリプティングとテストではJavaScriptを使用できます。Bruには組み込みライブラリがあり、必要に応じて外部JavaScriptライブラリもインストールできます。詳細な例が公式ドキュメントに記載されています。
組み込みライブラリ
スクリプトでインポートできる組み込みライブラリは以下の通りです:
-
ajv— Ajv JSONスキーマバリデータ
-
axios— ブラウザとNode.js用のPromiseベースのHTTPクライアント
-
node-fetch— Node.js用の軽量Fetch APIモジュール
-
atob— Base64エンコードされたASCIIデータをバイナリに変換
-
btoa— バイナリデータをBase64エンコードされたASCIIに変換
-
chai— Node.jsとブラウザ用のBDD/TDDアサーションライブラリ
-
lodash— モジュール性、パフォーマンス、拡張機能を提供する最新のJavaScriptユーティリティライブラリ
-
moment— JavaScriptでの日付と時刻の解析、検証、操作、表示
-
uuid— RFC4122 UUID生成用
-
nanoid— JavaScript用の小型で安全なURL対応の一意な文字列ID生成ツール
-
crypto-js— 暗号化標準のJavaScriptライブラリ
使用例:
const { nanoid } = require("nanoid"); req.setHeader("transaction-id", nanoid());
外部ライブラリ
外部ライブラリを使用する場合は、npmでインストールしてからスクリプトやテストで使用できます。
例:npm i @faker-js/faker
const { faker } = require('@faker-js/faker'); const randomName = faker.name.fullName(); const randomEmail = faker.internet.email(); req.setBody({ name: randomName, email: randomEmail });
スクリプトやテストではJavaScriptのトップレベルawaitが使用可能です。
JavaScript APIリファレンスとレスポンスクエリについてもご一読いただくことを強くお勧めします。Brunoのテストにはchaiを使用しています。
テストの主な利点:
-
効率性の向上:テストを繰り返し実行でき、手動テストに必要な時間と労力を削減できます。
-
カバレッジの向上:自動テストは手動テストと比較して、より多くのシナリオとエッジケースをカバーできます。
-
CI/CD(継続的インテグレーション/継続的デリバリー) :APIテストをCI/CDパイプラインに組み込むことで、APIの変更がデプロイ前に徹底的にテストされます。
-
メンテナンスの容易さ:APIの進化に合わせて自動テストを簡単に更新でき、手動テストと比較してメンテナンスの負担が軽減されます。
-
回帰テストの簡素化:APIに変更を加えた後、自動テストを簡単に再実行でき、回帰テストに必要な時間を削減できます。
例:
test("should be able to login", function() { const data = res.getBody(); expect(res.getStatus()).to.equal(200); }); test("should receive the token", function() { const data = res.getBody(); expect(data.token).to.be.a('string'); });
公式ドキュメントには多くの例が含まれており、全体的にコンパクトにまとまっています。15〜20分程度で全体を読むことができます。ぜひドキュメント全体を丁寧にお読みください。
実践編:始めましょう
理論は十分です。実践を始めましょう 😁
Brunoを使い始めるには、まずNode.jsをマシンにインストールする必要があります。私は Node.js のバージョン管理にVoltaを使用していますが、お好みのツールをご使用ください。
Node.jsをインストールした後、Brunoをインストールします:
npm install -g @usebruno/cli
REST APIが実装されているプロジェクトに移動します。
ここでは、REST APIのために json-server を使用します。
プロジェクトの作成:
-
フォルダの作成:mkdir api_test
-
フォルダへの移動:cd api_test
-
プロジェクトの初期化:npm init -y
bruno.jsonを追加して、Brunoにapi_testフォルダがコレクションであることを認識させます。
注:ここでのapi_testは、プロジェクトとBrunoのコレクションの両方を兼ねています。
-
bruno.jsonファイルの作成: touch bruno.json
-
bruno.jsonに以下の内容を追加します:
{ "version": "1", "name": "example rest api test", "type": "collection" }
高速なREST API開発のために、 json-serverを使用します。
-
json-serverのインストール: npm install -g json-server
-
employeeDB.jsonファイルを作成し、以下の内容を追加: touch employeeDB.json
{ "employees": [ { "id": 1, "name": "山田", "salary": "10000" }, { "id": 2, "name": "鈴木", "salary": "8000" } ] }
- routes.jsonを作成し、以下の内容を追加:touch routes.json
{ "/employees/list": "/employees", "/employees/get/:id": "/employees/:id", "/employees/create": "/employees", "/employees/update/:id": "/employees/:id", "/employees/delete/:id": "/employees/:id" }
上記のroutes.jsonファイルは、デフォルトのルートをカスタムルートにエイリアス設定しています。ルートのエイリアス設定はRESTアーキテクチャのエンドポイント命名規則の標準から外れますが、学習目的には有用です。
-
json-serverの起動:json-server --port 8000 --routes routes.json --watch employeeDB.json
-
以下のURLにアクセス:http://localhost:8000/employees/list
/employees/listへのアクセス

Brunoを使用したテスト方法
-
employee_brunoフォルダの作成:mkdir employee_bruno
-
list_employee.bruに以下の内容を追加して、従業員一覧を取得し、ステータスが200であることと結果の長さが2であることをテストします。touch employee_bruno/list_employee.bru
-
次にbruを実行:bru run employee_bruno
meta { name: List Employee type: http seq: 1 } get { url: <http://localhost:8000/employees/list> } headers { content-type: application/json } script:pre-request { console.log("Before the api hit!!!"); console.log("We can get the token or anything which require for the api"); } script:post-response { console.log(res.getBody()) console.log("After the api hit!!!"); console.log("We can set the token or anything which require for the api"); } tests { test("should have response status 200", function() { expect(res.getStatus()).to.equal(200); }); test("should have employees length 2", function() { const data = res.getBody(); expect(data.length).to.equal(2); }); }
- get_employee.bruに以下の内容を追加して、IDから従業員を取得し、ステータスが200であることをテストします。touch employee_bruno/get_employee.bru
meta { name: Get Employee type: http seq: 2 } get { url: <http://localhost:8000/employees/get/1> } script:post-response { console.log(res.getBody()) } tests { test("should have response status 200", function() { expect(res.getStatus()).to.equal(200); }); }
- create_employee.bruに以下の内容を追加して、従業員を作成し、ステータスが201であることと作成された従業員のIDが3であることをテストします。touch employee_bruno/create_employee.bru
meta { name: Create Employee type: http seq: 3 } post { url: <http://localhost:8000/employees/create> } headers { content-type: application/json } body { { "id": 3, "name": "Marry", "salary": 20000 } } script:post-response { console.log(res.getBody()) } tests { test("should have response status 201", function() { expect(res.getStatus()).to.equal(201); }); test("should have employee id 3", function() { const data = res.getBody(); expect(data.id).to.equal(3); }); }
- update_employee.bruに以下の内容を追加して、従業員を更新し、ステータスが200で従業員名がMaxであることをテストします。touch employee_bruno/update_employee.bru
meta { name: Update Employee type: http seq: 4 } put { url: <http://localhost:8000/employees/update/3> } headers { content-type: application/json } body { { "id": 3, "name": "Max", "salary": 20000 } } script:post-response { console.log(res.getBody()) } tests { test("should have response status 200", function() { expect(res.getStatus()).to.equal(200); }); test("should have employee name Max", function() { const data = res.getBody(); expect(data.name).to.equal("Max"); }); }
- delete_employee.bruに以下の内容を追加して、IDで従業員を削除し、ステータスが200であることをテストします。touch employee_bruno/delete_employee.bru
meta { name: Delete Employee type: http seq: 5 } delete { url: <http://localhost:8000/employees/delete/1> } script:post-response { console.log("DELETED!!!"); } tests { test("should have response status 200", function() { expect(res.getStatus()).to.equal(200); }); }
上記のテストは良いですが、以下のような機能が欲しいです:
-
最初に従業員を作成し、
-
リストに3人の従業員がいることを確認し、
-
最後に作成した従業員を動的に削除する。
上記で行っていたようなハードコードされた値は使用したくありません。また、ドメイン名もハードコードされているので、変数として追加できます。
詳しく見ていきましょう:
-
新しいディレクトリemployee_bruno_betterを作成:mkdir employee_bruno_better
-
environments ディレクトリを作成:mkdir environments
-
フォルダ内にEmployee.bruファイルを作成し、以下を追加:touch environments/Employee.bru
vars { baseUrl: <http://localhost:8000> }
- 最初に従業員を作成するのでseq: 1を追加します。また、bruの実行時にbaseUrlの値を取得するために--envを渡します:
bru run employee_bruno_better --env Employee
employee_bruno_better内のcreate_employee.bruに以下を追加
meta { name: Create Employee type: http seq: 1 } post { url: {{baseUrl}}/employees/create } headers { content-type: application/json } body { { "id": 3, "name": "Marry", "salary": 20000 } } script:post-response { bru.setVar("id", res.getBody().id); bru.setVar("createdEmployee", res.getBody()); } tests { test("should have response status 201", function() { expect(res.getStatus()).to.equal(201); }); test("should have employee id 3", function() { const data = res.getBody(); expect(data.id).to.equal(3); }); }
-
上記は単純ですが、外部ライブラリfakerを使用して名前フィールドを埋めてみましょう:
-
fakerライブラリをインストール:npm i @faker-js/faker
meta { name: Create Employee type: http seq: 1 } post { url: {{baseUrl}}/employees/create } headers { content-type: application/json } script:pre-request { const { faker } = require('@faker-js/faker'); const randomName = faker.name.fullName(); req.setBody({ "id": 3, "name": randomName, "salary": 20000 }) } script:post-response { bru.setVar("id", res.getBody().id); bru.setVar("createdEmployee", res.getBody()); } tests { test("should have response status 201", function() { expect(res.getStatus()).to.equal(201); }); test("should have employee id 3", function() { const data = res.getBody(); expect(data.id).to.equal(3); }); }
- 同様にID 3の従業員を取得するために、get_employee.bruに以下を追加できます。注:idは上記のbruファイルで設定されています。
meta { name: Get Employee type: http seq: 3 } get { url: {{baseUrl}}/employees/get/{{id}} } script:pre-request { const e = bru.getVar("createdEmployee"); console.log(typeof(e), e) } script:post-response { console.log("RES::", res.getBody()) } tests { test("should have response status 200", function() { expect(res.getStatus()).to.equal(200); }); }
これでファイルをリポジトリにプッシュできます。変数にはOSの環境変数または.envファイルも使用できます。DotEnvファイルとシークレット変数についてのドキュメントを参照して、認証情報をGitリポジトリにプッシュしないようにしてください。完全な例についてはGitHubリポジトリを参照してください。
Bruno GUIアプリケーションを使用して、bruno.jsonファイルを含むフォルダのコレクションを開くこともできます。Bruno GUIアプリケーションはこちらからダウンロードできます。

Bruno GUIアプリケーション
Brunoのインストール方法(デスクトップアプリとCLI)
デスクトップアプリは公式サイトのダウンロードページから入手できるほか、各OSのパッケージマネージャーでも導入できます。
# macOS
brew install bruno
# Windows
winget install Bruno.Bruno
choco install bruno
scoop install bruno
# Linux
sudo apt update && sudo apt install bruno
sudo snap install bruno
flatpak install flathub com.usebruno.Bruno
コマンドラインツール(bru コマンド)は、記事の実践編で使ったとおり Node.js 環境に npm で入れます。GUI と CLI は別々に導入するもので、どちらか一方だけでも使えます。
npm install -g @usebruno/cli
GUIとCLIの使い分け
GUI はリクエストの作成と1件ずつの実行、そしてコレクションランナーによる一括実行に向いています。ランナーはサイドバーでコレクションの「···」メニューから Run を選ぶか、上部バーのランナーアイコンから開き、リクエスト間の待ち時間などを設定して実行します。ランナーが実行するのは HTTP リクエストだけで、gRPC と WebSocket のリクエストは対象外です。
CI/CD で自動実行するときは CLI を使います。コレクションのフォルダで bru run を実行すると全リクエストが順に実行され、フォルダ名を渡せばその配下だけを実行できます。環境や変数の指定、タグによる絞り込み、レポート出力も CLI 側の機能です。
# コレクション全体を実行
bru run
# users フォルダ配下だけ実行
bru run users
# 環境を指定して実行(変数の上書きも可能)
bru run --env Local
bru run --env Local --env-var JWT_TOKEN=1234
# タグで絞り込み
bru run --tags=smoke,sanity
# レポートを出力(複数同時に指定可)
bru run --reporter-json results.json --reporter-junit results.xml --reporter-html results.html
CLI の 3.0.0 以降は、既定の実行モードが Safe Mode に変わりました。スクリプトで開発者向けの機能を使う場合は --sandbox=developer を明示します。
環境変数とbaseUrlの設定
ローカルと本番で接続先を切り替えるには、環境変数を使います。環境はコレクション内の environments フォルダに、環境ごとの .bru ファイル(YAML 形式のコレクションでは .yml)として保存されます。たとえば local.bru は次のようになります。
vars {
host: http://localhost:8787
token: abc123
}
リクエストからは {{host}} のように二重波括弧で参照します。記事の例で直接書いていた URL を、baseUrl に当たる変数に置き換えると、同じリクエストをローカルと本番の両方で実行できます。
get {
url: {{host}}/employees/list
}
GUI では右上の「No environment」から使う環境を選びます。既定の環境はコレクション設定の Presets タブで指定でき、チームの誰が開いても同じ環境が自動で読み込まれます。CLI では --env で環境名を渡し、省略した場合は既定の環境が使われます。コレクション全体で共通の値は、Collection Settings の Variables タブにあるコレクション変数に置くと、リクエストごとに繰り返し書かずに済みます。
トークンや API キーは、記事前半で触れたシークレット変数として扱い、Git に含めないでください。環境ファイルにそのまま書いたままコミットするのが、最も起きやすい事故です。
コレクションのファイル形式(BruとYAML)
Bruno 3.0.0 から、リクエストの定義を .bru に加えて YAML(.yml)で保存できるようになりました。YAML 形式は Bruno が策定した OpenCollection 仕様に沿っており、ルートのマーカーファイルが bruno.json ではなく opencollection.yml になります。両形式は同じコレクション内で共存できるので、段階的に移行でき、既存の .bru コレクションは公式の移行ガイドで YAML に変換できます。この記事の Bru 形式の例は、そのまま使えます。
Gitでのチーム共有と運用
Bruno の強みは、Git や GitHub、GitLab、Bitbucket といった普段の仕組みでそのまま共同作業できることです。コレクションはこの記事の api_test のようにソースコードと同じリポジトリに置くか、API 仕様専用のリポジトリに置きます。
新しいコレクションを作ると、一時ファイルとキャッシュ、機密情報を含む環境変数ファイル、OS 固有のファイルを除外する .gitignore が自動で生成されます。API キーの入った環境ファイルをうっかりコミットしないための仕組みなので、消さずに残してください。
チームメンバーはリポジトリをクローンし、Bruno で bruno.json(または opencollection.yml)を含むフォルダを開くだけです。親フォルダを開くと、その配下にある複数のコレクションをまとめて検出できるので、モノレポでも扱えます。
3.0.0 以降の GUI には Git 機能が組み込まれ、無料版でも初期化、差分の表示、pull ができます。コミットと push は Pro または Ultimate の機能なので、無料版ではターミナルの git コマンドで運用します。API の変更は .bru ファイルの差分として現れるため、コレクションの変更をプルリクエストに含めておくと、API 仕様の変更履歴をコードレビューと同じ流れで追えます。
これは氷山の一角に過ぎず、Brunoを使用して多くのことができます。このブログを気に入っていただければ幸いです。
Anoop M D氏とメンテナの皆様、Brunoの開発ありがとうございます。🫡
この記事は、2025年1月に当社のエンジニア Mukesh Chaudhary が執筆した内容を日本語に翻訳したものです。
英語版はこちらをご覧ください。
https://articles.wesionary.team/bruno-a-better-api-client-for-developers-38b8c7d1d0de
採用情報
私たちはプロダクト共創の仕組み化に取り組んでいます。プロダクト共創をリードするプロダクト・マネージャー、そして、私たちのビジョンを市場に届ける営業メンバーを募集しています!
https://wesionary.team/career/product-manager
https://wesionary.team/career/sales
開発パートナーをお探しの企業様へ
当社は、グローバル開発のメリットを活かし、高い費用対効果と品質を両立しています。経験豊富で多様性のあるチームが、課題を正しく理解し、最適なシステムと優れた体験を実現します。業務システムの開発、新規事業の開発、業務効率化やDX化に関するお困りごと、ぜひ当社にご相談ください。