RawCode Studio Logo
← Wróć do listy artykułów pl

Mikroserwis do generowania wykresów — PNG, JSON i embed z jednego API

Jak zbudowałem mikroserwis w Node.js + TypeScript, który generuje wykresy w trzech formatach: PNG dla PDF-ów, JSON dla SPA i gotowy embed — z jednego requesta.

Publikacja: 2026-03-11 Szacowany czas czytania: 10 min

Od problemu z PDF-em do mikroserwisu

Zaczęło się od potrzeby prawidłowego generowania wykresów w automatycznych raportach do klientów. Moim konkretnym use case była sytuacja, w której biblioteki do generowania PDF nie radziły sobie z poprawnym renderowaniem wykresów. W głowie miałem kilka pomysłów, w tym testowanie innych bibliotek, ale doszła jeszcze potrzeba wykorzystania generowania w automatyzacjach. Co więcej, wszelkie wysłane wykresy powinny mieć spójny motyw wizualny zgodny z identyfikacją wizualną firmy. I tutaj wpadłem na pomysł, że owszem, możemy ten sam mechanizm generowania wykresów implementować kilka razy:

Jedna odpowiedzialność, jedno miejsce

Pierwsza decyzja była prosta: izolacja. Chciałem serwisu, który robi jedną rzecz — generuje i serwuje wykresy — i robi ją dobrze. Nie kolejnego monolitu z "modułem wykresów" zakopanym gdzieś w folderze utils/.

Mikroserwis oznacza, że każda aplikacja w ekosystemie wywołuje go przez HTTP i dostaje to czego potrzebuje. Jeden endpoint autoryzowany kluczem API. Niezależny deployment. Można go podmienić, skalować lub wyłączyć bez dotykania reszty systemu.

Stack wybrałem pragmatycznie: Node.js + TypeScript + Express. TypeScript daje bezpieczeństwo typów bez ceremonii Javy, Express jest wystarczająco prosty żeby nie wchodzić w drogę. Do persystencji — PostgreSQL, do cache'owania — Redis. Celowo nie użyłem żadnego ORM: przy tej skali SQL jest czytelniejszy i łatwiejszy do debugowania niż warstwy abstrakcji.

Jedna rzecz, z której jestem szczególnie zadowolony: serwis działa poprawnie nawet bez Redisa. Jeśli cache nie jest dostępny, odpytuje bazę danych bezpośrednio. Graceful degradation zamiast twardej zależności — coś co przy pierwszym projekcie pewnie bym pominął.

Jeden wykres, trzy formaty

Tu zaczyna się część, z której jestem najbardziej zadowolony.

Generujesz wykres jednym requestem:

curl -X POST https://charts.example.com/api/charts/generate \
  -H "x-api-key: your-key" \
  -H "Content-Type: application/json" \
  -d '{
    "chartType": "bar",
    "title": "Ruch organiczny Q1 2026",
    "data": {
      "labels": ["Styczeń", "Luty", "Marzec"],
      "datasets": [{"label": "Sesje", "data": [12400, 15800, 18200]}]
    }
  }'

W odpowiedzi dostajesz data.chart_hash — unikalny identyfikator wykresu, a także gotowe URLe do wszystkich formatów. I od tej chwili ten sam hash możesz użyć na trzy różne sposoby.

/json — dla aplikacji frontendowych

GET /api/charts/{hash}/json zwraca pełną strukturę danych wykresu. Idealne dla SPA, które chce renderować wykres lokalnie przez Chart.js, ale trzymać dane po stronie serwera.

/png — dla PDF-ów i e-maili

GET /api/charts/{hash}/png zwraca binarny obraz PNG. Pod spodem działa Puppeteer z headless Chrome — renderuje interaktywny wykres Chart.js i robi screenshota. Jakość jak z przeglądarki, bez żadnego zewnętrznego narzędzia po stronie klienta.

To rozwiązuje problem, od którego zaczął się cały projekt: biblioteki PDF dostają gotowy PNG zamiast próbować samodzielnie renderować wykres.

/embed — dla dowolnej strony

RawCharts service open api dashboard

GET /api/charts/{hash}/embed zwraca gotową stronę HTML z osadzonym wykresem. Możesz ją wrzucić jako iframe gdziekolwiek:

<iframe
  src="https://charts.example.com/api/charts/a3f8c2/embed"
  width="600"
  height="400"
  frameborder="0">
</iframe>

Dokładnie jak embed YouTube czy Google Maps. Żadnych zależności frontendowych, żadnego JavaScript po stronie konsumenta.


Kluczowa obserwacja: dane wprowadzasz raz, format wybiera konsument. Dashboard używa /json, generator PDF pobiera /png, newsletter wkleja /embed. Jeden wykres, trzy miejsca — problem z początku artykułu rozwiązany.

To nie jest prototyp

Celowo nie zatrzymałem się na "działa lokalnie". Kilka rzeczy które dodałem żeby serwis był gotowy do produkcji:

API key auth — klucz API w nagłówku x-api-key jest wymagany do operacji zapisu: generowania, aktualizacji i usuwania wykresów. Odczyt (pobieranie PNG, embeda, danych JSON) działa przez osobny mechanizm share tokenów — bez ujawniania głównego klucza.

Rate limiting — domyślnie ograniczenie per IP. Ochrona przed nadużyciami bez potrzeby zewnętrznych narzędzi.

Redis cache — wygenerowany wykres jest cachowany. Jeśli ktoś odpytuje ten sam hash wielokrotnie (typowe przy embed), serwis nie trafia za każdym razem do bazy.

Swagger/OpenAPI — dokumentacja generowana automatycznie z kodu. Każdy developer może otworzyć /api/docs i od razu zacząć eksperymentować.

Health checks — endpointy /api/health i /api/health/detailed gotowe pod monitoring i orchestrację (Kubernetes, Docker Swarm, load balancery).

Publiczne/prywatne wykresy — każdy wykres ma share_token. Jeśli token jest ustawiony, dostęp do odczytu wymaga jego podania w query (?token=) lub nagłówku (x-share-token). Jeśli token jest pusty — wykres jest publiczny i embed działa bez żadnej autoryzacji. To jest dokładnie to czego potrzebujesz przy osadzaniu na zewnętrznej stronie.

Czego się nauczyłem

Przede wszystkim, że mikroserwis może być sposobem na spłatę długu technicznego — bez walki z nim wewnątrz monolitu. W moim przypadku długiem były biblioteki PDF, które nie radziły sobie z renderowaniem wykresów. Zamiast szukać obejść w kontekście istniejącego projektu — z jego zależnościami, ograniczeniami i historią decyzji — stworzyłem osobne miejsce, gdzie jedynym zadaniem jest poprawne wygenerowanie wykresu. Tam mogłem dobrać narzędzia od zera: stąd Puppeteer zamiast kolejnej biblioteki, która też by zawodziła. Standardowe API sprawia zaś, że wpięcie takiego serwisu w oryginalny projekt jest proste — kilka linii kodu niezależnie od tego w czym napisany jest monolit.