
API開発の効率化を加速!Brunoが実現する新しいプロジェクト管理手法
API クライアント技術における革新。

ブロートウェアにさようなら、シンプルさ、効率性、自由を手に入れましょう。
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:GETHTTPリクエストを実行します。
get {
url: <https://api.github.com/users/usebruno>
}
3. post:POSTHTTPリクエストを実行します。
post {
url: <https://api.github.com/users/usebruno>
}
4. put:PUTHTTPリクエストを実行します。
put {
url: <https://api.github.com/users/usebruno>
}
5. delete:DELETEHTTPリクエストを実行します。
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を使用して多くのことができます。このブログが気に入っていただければ幸いです。
Anoop M D氏とメンテナの皆様、Brunoの開発ありがとうございます。🫡
この記事は、2024 年 1 月に弊社のエンジニア Mukesh Chaudhary が執筆し、日本語に翻訳したものです。
英語版はこちらをクリックしてください。
https://articles.wesionary.team/bruno-a-better-api-client-for-developers-38b8c7d1d0de
採用情報
私たちはプロダクト共創の仕組み化に取り組んでいます。プロダクト共創をリードするプロダクト・マネージャー、そして、私たちのビジョンを市場に届ける営業メンバーを募集しています!
開発パートナーをお探しの企業様へ
弊社は、グローバル開発のメリットを活かし、高い費用対効果と品質を両立しています。経験豊富で多様性のあるチームが、課題を正しく理解し、最適なシステムと優れた体験を実現します。業務システムの開発、新規事業の開発、業務効率化やDX化に関するお困りごと、ぜひ弊社にご相談ください。