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:
- w panelu klienta,
- w workflow do generowania raportów,
- w wewnętrznym CRM, gdzie ręcznie kopiuje się wyniki do maila, albo zrobić specjalistyczne narzędzie przygotowane do tego jednego celu. Więc postawiłem właśnie na podejście mikroserwisowe. Tak zrodził się
raw-chart-service.
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

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.