Bruno, klient API, który mieszka w repozytorium
Bruno zapisuje kolekcje zapytań jako zwykłe pliki tekstowe w katalogu projektu. Wersjonujesz je gitem razem z kodem, przeglądasz zmiany w pull requeście i nie potrzebujesz konta ani synchronizacji z cudzą chmurą. Aplikacja działa offline, a rdzeń jest darmowy i otwarty.
Skąd wziął się pomysł
Klasyczne klienty API trzymają kolekcje we własnej chmurze i wiążą je z kontem użytkownika. Wynikają z tego trzy powtarzalne problemy: kolekcja rozjeżdża się z rzeczywistym API, bo nikt nie pamięta o aktualizacji, zmiana wprowadzona przez jedną osobę jest niewidoczna dla reszty do momentu synchronizacji, a dział bezpieczeństwa pyta, gdzie właściwie lądują adresy wewnętrznych usług i tokeny.
Bruno odwraca ten układ. Kolekcja to katalog z plikami .bru, leżący obok kodu, który opisuje. Aktualizacja endpointu i aktualizacja zapytania trafiają do tego samego commita, więc rozjazd rzuca się w oczy w przeglądzie kodu.
Efekt uboczny bywa ważniejszy niż sama funkcja: skoro kolekcja jest w repozytorium, nowa osoba w zespole dostaje ją razem z projektem. Nie ma etapu zapraszania do przestrzeni roboczej ani szukania, kto ma najnowszą wersję.
Format bru w praktyce
Plik zapytania jest czytelny dla człowieka i dla narzędzi porównujących zmiany.
meta {
name: Utworz zamowienie
type: http
seq: 3
}
post {
url: {{bazowyAdres}}/api/orders
body: json
}
headers {
Authorization: Bearer {{token}}
}
body:json {
{
"produktId": "{{produktId}}",
"ilosc": 2
}
}Różnica wobec formatu JSON eksportowanego przez inne narzędzia jest praktyczna. Diff pokazuje, że zmienił się nagłówek, a nie że przestawiło się dwieście linii z powodu innej kolejności kluczy. Konflikt scalania da się rozwiązać ręcznie, bo plik czyta się jak konfigurację, a nie jak zrzut struktury.
Zmienne środowiskowe trzymasz osobno, w plikach środowisk. Wrażliwe wartości zostawiasz poza repozytorium i wstrzykujesz je zmiennymi systemowymi albo z pliku pominiętego w .gitignore.
Testy przy zapytaniu
Do każdego zapytania dopisujesz asercje w JavaScripcie, które wykonują się po odpowiedzi.
test("zwraca 201 i identyfikator zamowienia", function() {
expect(res.getStatus()).to.equal(201)
expect(res.getBody().id).to.be.a("string")
})
test("odpowiedz przychodzi ponizej 500 ms", function() {
expect(res.getResponseTime()).to.be.below(500)
})Skrypty przed zapytaniem i po nim pozwalają zbudować sekwencję: zaloguj się, zapisz token do zmiennej, użyj go w kolejnych krokach. To wystarcza do testów kontraktowych, czyli sprawdzenia, czy API nadal odpowiada tym, czym obiecało.
Asercje warto pisać na to, co obiecuje kontrakt, a nie na dokładny kształt odpowiedzi. Sprawdzenie, że pole istnieje i ma właściwy typ, przetrwa dodanie nowego pola przez zespół backendu. Porównanie całej odpowiedzi z zapisanym wzorcem wywali się przy pierwszej takiej zmianie i nauczy zespół ignorowania czerwonych testów.
Nie jest to jednak zamiennik testów w kodzie. Logikę biznesową testuj w projekcie, a Bruno traktuj jako warstwę sprawdzającą styk między usługami oraz jako dokumentację, którą da się uruchomić.
Uruchamianie w pipeline
Klient wiersza poleceń wykonuje całą kolekcję bez interfejsu, więc te same zapytania służą do sprawdzania środowiska po wdrożeniu.
npm install -g @usebruno/cli
bru run --env staging --reporter-junit wynik.xmlRaport w formacie JUnit czyta większość systemów ciągłej integracji, więc nieudany test kontraktowy zatrzymuje wdrożenie tak samo jak nieudany test jednostkowy. To najczęściej pomijana wartość tego narzędzia: kolekcja przestaje być prywatną zabawką jednej osoby i staje się częścią pipeline'u.
Praktyczny układ wygląda tak: po wdrożeniu na środowisko testowe uruchamiasz kolekcję z asercjami na kluczowych ścieżkach. Przy dwudziestu zapytaniach przebieg trwa kilkanaście sekund, a wyłapuje błędy konfiguracji, których testy jednostkowe nie zobaczą, bo nie dotykają sieci.
Uwierzytelnianie i sekrety
Większość API wymaga tokenu, a jego zdobycie to zwykle osobne zapytanie. Zamiast wklejać wartość ręcznie przy każdej sesji, zapisz ją w skrypcie po odpowiedzi i używaj jako zmiennej.
const dane = res.getBody()
bru.setEnvVar("token", dane.access_token)Od tej chwili kolejne zapytania odwołują się do {{token}}, a odświeżenie sprowadza się do ponownego uruchomienia jednego wywołania. Przy API z krótkim czasem życia tokenu warto dopisać ten krok jako pierwszy w kolejności wykonywania kolekcji, żeby przebieg w pipelinie zaczynał się od uwierzytelnienia.
Sekretów nie trzymaj w plikach środowisk, które trafiają do repozytorium. Bezpieczny układ ma trzy warstwy: plik środowiska z adresami i nazwami zmiennych w repozytorium, wartości wrażliwe w pliku lokalnym wykluczonym z gita, a w pipelinie zmienne wstrzykiwane z magazynu sekretów systemu ciągłej integracji.
bru run --env staging --env-var token=$API_TOKENTen podział ma jeszcze jedną zaletę. Nowa osoba dostaje razem z repozytorium komplet zapytań i wie, jakich wartości brakuje, bo nazwy zmiennych są widoczne. Zamiast zgadywać, prosi o dwa konkretne sekrety.
Jeśli tokeny wcześniej trafiły do historii gita, samo usunięcie ich z pliku nie wystarczy. Trzeba je unieważnić po stronie dostawcy, bo w historii zostają dostępne dla każdego, kto sklonuje repozytorium.
Organizacja kolekcji w większym projekcie
Kolekcja z trzydziestoma zapytaniami w jednym katalogu przestaje być czytelna po miesiącu. Struktura, która sprawdza się w dłuższej perspektywie, odzwierciedla podział usług, a nie kolejność ich powstawania.
Rozsądny układ to katalog na zasób i wewnątrz zapytania w kolejności odpowiadającej cyklowi życia: utworzenie, odczyt, aktualizacja, usunięcie. Pole seq w metadanych ustala kolejność wykonywania, co ma znaczenie przy przebiegu całej kolekcji w pipelinie, gdzie kolejne kroki zależą od wcześniejszych.
Przy monorepo warto trzymać kolekcję blisko usługi, którą opisuje, a nie w jednym wspólnym katalogu na szczycie. Zmiana w API i zmiana w zapytaniu trafiają wtedy do tego samego obszaru pull requesta i widzi je ta sama osoba przeglądająca kod.
Nazewnictwo ma większe znaczenie, niż się wydaje, bo nazwa pliku jest tym, co widać w diffie. Nazwa opisująca działanie, na przykład utworzenie zamówienia z rabatem, mówi recenzentowi więcej niż sam adres endpointu, który i tak jest w treści pliku.
Ostatnia rzecz to porządkowanie. Kolekcje mają tendencję do zbierania zapytań tworzonych ad hoc przy diagnozowaniu problemów. Raz na kwartał warto przejrzeć katalog i usunąć te, które nie opisują żadnej realnej ścieżki, bo inaczej po roku połowa zawartości to eksperymenty, których nikt nie rozumie.
Bruno kontra alternatywy
| Narzędzie | Mocna strona | Słabość | Kiedy wybrać |
|---|---|---|---|
| Bruno | Kolekcje w repozytorium, offline, bez konta | Mniej gotowych integracji i mniejszy ekosystem | Zespół chce wersjonować zapytania razem z kodem |
| Postman | Największy ekosystem, mocna współpraca w chmurze | Wymaga konta, dane w chmurze dostawcy | Duża organizacja korzystająca z monitoringu i mocków |
| Insomnia | Czysty interfejs, dobra obsługa GraphQL | Zmiany modelu licencyjnego w przeszłości | Praca głównie z GraphQL |
| Hoppscotch | Działa w przeglądarce, nic nie instalujesz | Ograniczenia przy zapytaniach do usług lokalnych | Szybkie sprawdzenie endpointu bez instalacji |
| curl | Zawsze pod ręką, świetny do skryptów | Brak organizacji i historii | Pojedyncze wywołanie w terminalu |
Wybór zależy głównie od tego, czy kolekcja ma być artefaktem zespołu, czy usługą. Jeśli zespół pracuje w gicie i unika kolejnych kont, Bruno wpisuje się w ten nawyk. Jeśli korzystacie z monitoringu API i serwerów mockujących u jednego dostawcy, przesiadka niewiele da.
Licencja i model płatności
Rdzeń aplikacji jest darmowy i otwarty, i to się nie zmienia. Wokół niego dostawca sprzedaje wsparcie i funkcje dla organizacji.
Warto znać jedną zmianę, bo krąży w sieci sporo nieaktualnych informacji. Wariant Golden Edition, kupowany jednorazowo za 19 USD, został wycofany dla nowych klientów. Powodem było to, że licencje przeznaczone dla osób indywidualnych kupowały głównie firmy, co nie odpowiadało założeniu. Osoby, które kupiły go wcześniej, zachowują dostęp bezterminowo. Obecna oferta to wersja otwarta i dwa plany płatne, oba rozliczane w cyklu rocznym.
| Plan | Koszt | Przestrzenie robocze | Repozytoria w gitowym interfejsie |
|---|---|---|---|
| Open Source | 0 USD | 2 | tylko publiczne |
| Pro | 6 USD za użytkownika miesięcznie, rozliczane rocznie | bez limitu | publiczne i prywatne |
| Ultimate | 11 USD za użytkownika miesięcznie, rozliczane rocznie | bez limitu | publiczne i prywatne |
Stawki 6 i 11 USD to kwoty przy płatności za rok z góry, bo dostawca nie podaje wariantu miesięcznego. Planując budżet, mnóż je przez dwanaście zamiast zakładać opłatę, z której da się zrezygnować w dowolnym miesiącu. Ultimate ma czternastodniowy okres próbny bez podawania karty.
Wersja darmowa nie jest przy tym bez granic. Mieści dwie przestrzenie robocze, pięć synchronizacji OpenAPI miesięcznie, a wbudowany interfejs gitowy łączy się w niej wyłącznie z publicznymi repozytoriami. Wsparcie sprowadza się do społeczności, bez gwarantowanego czasu odpowiedzi. Samo trzymanie kolekcji w repozytorium i praca zwykłym gitem z terminala pozostają przy tym darmowe, bo pliki .bru leżą na dysku i nie potrzebują żadnej integracji.
Ten szczegół ma znaczenie przy wpisywaniu narzędzia do stosu firmowego. Sprawdź aktualny cennik przed przyjęciem założenia o jednorazowej opłacie, bo materiały z 2024 roku nadal ją opisują.
Migracja z Postmana
Import kolekcji działa i jest punktem wyjścia, nie końcem pracy. Przenoszą się zapytania, nagłówki, środowiska i większość skryptów, natomiast rzeczy specyficzne dla poprzedniego narzędzia wymagają ręcznej poprawki.
Uwagi z praktyki są trzy. Skrypty korzystające z obiektu specyficznego dla Postmana trzeba przepisać na odpowiedniki Bruna, co przy kilkunastu zapytaniach zajmuje godzinę. Zmienne globalne rozsypane po przestrzeni roboczej warto przy okazji uporządkować w pliki środowisk. Sekrety wklejone kiedyś na sztywno w nagłówki trzeba wyłapać, zanim kolekcja trafi do repozytorium, bo od tego momentu zostają w historii gita.
Rozsądna kolejność to migracja jednej kolekcji, przepracowanie z nią tygodnia i dopiero potem przenoszenie reszty. Import wszystkiego naraz kończy się katalogiem z setką zapytań, z których połowa nie działa, i zniechęceniem zespołu.
Kolekcja jako dokumentacja API
Dokumentacja API starzeje się szybciej niż jakikolwiek inny dokument w projekcie, bo nikt nie zauważa, że przestała być prawdziwa. Kolekcja uruchamiana automatycznie ma tę przewagę, że sama się do tego przyznaje: przestaje przechodzić.
Żeby to działało, zapytania muszą opisywać realne przypadki użycia, nie tylko poprawną ścieżkę. Obok wywołania zwracającego 201 warto trzymać wariant z brakującym polem i sprawdzać, czy API zwraca 400 z sensownym komunikatem. Ten drugi przypadek psuje się częściej, bo rzadziej się go testuje, a to on ląduje w rękach klienta integrującego się z Twoim API.
Dobrą praktyką jest opis w polu docs przy zapytaniu. Kilka zdań o tym, kiedy używa się tego wywołania i czego się po nim spodziewać, kosztuje minutę, a oszczędza pytania na czacie zespołu. W przeciwieństwie do zewnętrznego dokumentu ten opis leży obok zapytania, więc aktualizuje go ta sama osoba, która zmienia zachowanie.
Warto też odróżnić dwie rzeczy, które łatwo pomylić. Specyfikacja OpenAPI opisuje kształt API formalnie i nadaje się do generowania klientów. Kolekcja Bruna pokazuje, jak faktycznie się z niego korzysta, razem z kolejnością wywołań i realnymi wartościami. Te dwie warstwy uzupełniają się, a nie zastępują.
Przy publicznym API kolekcja bywa też najszybszym sposobem, żeby ktoś z zewnątrz zaczął pracę. Katalog z zapytaniami w repozytorium jest konkretniejszy niż strona z przykładami, bo można go uruchomić bez przepisywania czegokolwiek.
Typowe błędy
Pierwszy to trzymanie tokenów w plikach kolekcji. Wszystko, co wejdzie do repozytorium, zostaje w historii, więc wartości wrażliwe wstrzykuj przez zmienne środowiskowe.
Drugi to traktowanie kolekcji jako dokumentacji bez uruchamiania jej w pipeline. Kolekcja, której nikt nie wykonuje automatycznie, dezaktualizuje się w tym samym tempie co dokument w edytorze tekstu.
Trzeci to duplikowanie zapytań zamiast używania zmiennych. Dziesięć wariantów tego samego wywołania z różnymi adresami zamienia się w problem przy pierwszej zmianie ścieżki.
Czwarty to opisywanie w kolekcji wyłącznie ścieżek, które działają. Zestaw samych poprawnych wywołań wygląda dobrze i niczego nie pilnuje, bo błędy w API najczęściej dotyczą przypadków brzegowych, a nie tego, co i tak sprawdzasz ręcznie przy każdym wdrożeniu.
Piąty to pomijanie środowisk. Praca na jednym środowisku z ręcznie podmienianym adresem prędzej czy później kończy się zapytaniem wysłanym na produkcję zamiast na test.
FAQ
Czy Bruno jest darmowy?
Rdzeń aplikacji jest darmowy i otwarty, wystarcza do codziennej pracy z API. Płatne są plany Pro za 6 USD i Ultimate za 11 USD za użytkownika miesięcznie przy rozliczeniu rocznym, obejmujące wsparcie i funkcje zespołowe. Jednorazowa licencja Golden Edition została wycofana dla nowych klientów, a osoby, które ją kupiły, zachowują dostęp.
Czy da się przenieść kolekcje z Postmana?
Tak, import obsługuje kolekcje i środowiska. Ręcznej poprawki wymagają skrypty korzystające z obiektów specyficznych dla Postmana oraz uporządkowanie zmiennych. Sensownie jest migrować kolekcja po kolekcji, nie wszystko naraz.
Czy Bruno obsługuje GraphQL?
Tak, obok zapytań REST obsługuje GraphQL wraz z osobnym polem na zmienne. Jeśli GraphQL jest głównym elementem projektu, warto porównać wygodę pracy z Insomnią, która ma w tym obszarze rozbudowane wsparcie.
Czy działa bez internetu?
Tak, to jedno z założeń projektu. Aplikacja nie wymaga konta ani logowania, a dane pozostają na dysku. Połączenie sieciowe jest potrzebne wyłącznie do wysyłania samych zapytań.
Czy nadaje się do testów w ciągłej integracji?
Tak, przez klienta wiersza poleceń, który wykonuje kolekcję i zwraca raport w formacie JUnit. To wystarcza do testów kontraktowych po wdrożeniu, natomiast do testów obciążeniowych lepiej użyć narzędzia stworzonego do tego celu.
Dokumentacja stoi na docs.usebruno.com, a kod źródłowy w repozytorium na GitHubie.