Node.js Best Practices — 108 praktyk, które warto przeczytać z poprawką na 2026 rok
Najpopularniejsza lista dobrych praktyk Node.js ma 105 tysięcy gwiazdek, 108 punktów w ośmiu sekcjach i licencję CC-BY-SA. Ma też „edycję 2026”, która w rzeczywistości jest commitem zmieniającym dwie linie. Pokazujemy, co z tej listy jest nadal wartościowe, czego w niej brakuje po zmianach w Node 20–26 i jak jej używać w code review.
Node.js Best Practices to najpopularniejsza lista dobrych praktyk dla Node'a: 105 597 gwiazdek, 10 723 forki, osiem sekcji, 108 punktów. Nie jest to dokumentacja ani framework, tylko kuratorowany zbiór zaleceń — każdy punkt ma streszczenie, kontrprzykład („co się stanie, jeśli tego nie zrobisz”) i odnośnik do dłuższego omówienia z przykładami kodu. Repozytorium: github.com/goldbergyoni/nodebestpractices.
Ten wpis nie jest streszczeniem listy — streszczanie listy nie ma sensu, lista sama jest streszczeniem. Jest o czymś innym: co z niej realnie warto wziąć, gdzie jej wiek zaczyna przeszkadzać i co trzeba dołożyć samodzielnie, bo w Node zmieniło się od 2023 roku więcej, niż widać po numerze wersji.
Co to jest i jak jest zbudowane
Struktura jest prosta i to jej zaleta. Osiem sekcji, w każdej od kilku do kilkudziesięciu punktów:
- Architektura projektu (6 punktów) — podział na komponenty biznesowe, warstwy, konfiguracja, wybór frameworka, TypeScript,
- Obsługa błędów (13) — async/await, rozszerzanie
Error, rozróżnienie błędów operacyjnych i katastrofalnych, centralna obsługa, testowanie ścieżek błędów, - Wzorce i styl kodu (13) — ESLint, konwencje nazw,
constzamiastlet, brak efektów ubocznych poza funkcjami, - Testy i jakość (13) — testy komponentowe, wzorzec AAA, nazwy testów, pokrycie, mockowanie zewnętrznego HTTP,
- Wyjście na produkcję (19) — monitoring, logowanie, reverse proxy, blokowanie zależności, wykorzystanie rdzeni CPU, bezstanowość,
npm ci, LTS, - Bezpieczeństwo (27) — reguły bezpieczeństwa w linterze, limitowanie żądań, sekrety, wstrzyknięcia SQL, nagłówki HTTP, bcrypt, walidacja JSON-a, unieważnianie JWT, uruchamianie bez roota, ReDoS,
- Wydajność (2, oznaczone jako szkic) — nie blokuj pętli zdarzeń, wybieraj metody natywne zamiast bibliotek pomocniczych,
- Docker (15) — budowanie wielostopniowe, uruchamianie przez
nodezamiastnpm start,.dockerignore, limity pamięci, jawne tagi obrazów, warstwy cache'u.
Do tego 630 plików w katalogu sections/ z rozwinięciami poszczególnych punktów, 88 odnośników „Read More” i dziesięć tłumaczeń. Punkty są otagowane — #new, #updated i #strategic — więc przy powtórnej lekturze da się przeczytać tylko to, co się zmieniło. Formalnie licencja to CC-BY-SA 4.0, o czym niżej, bo to nie jest szczegół.
Rzeczywistość „edycji 2026”
Nagłówek repozytorium ogłasza edycję 2026: modernizację treści, nowe rekomendowane biblioteki i nowe praktyki. Sprawdziliśmy, co za tym stoi, bo to determinuje sposób czytania całej listy.
Commit ogłaszający edycję 2026 nosi datę 15 czerwca 2026 i zmienia dwie linie w jednym pliku — dwie dodane, dwie usunięte. Wcześniejsze zmiany w repozytorium to jeden zmergowany pull request z kwietnia 2025 i garść z 2024. Zasadnicza treść pochodzi z okolic 2023 roku.
Widać to po plakietkach w nagłówku, których nikt nie ruszył: „Last update — January 24, 2023” i „Updated For Version — Node 22.0.0”. W jednym dokumencie krążą też trzy różne liczby punktów: plakietka mówi 102, akapit powitalny „więcej niż 80”, a policzone punkty to 108 (spis treści przy sekcji bezpieczeństwa mówi 25, a punktów jest tam 27). W nagłówku wisi zaproszenie na kurs z promocją „do 7 sierpnia”, bez podania roku. Sekcja o wydajności — akurat ta, w której Node ma najwięcej do powiedzenia — od lat jest szkicem z dwoma punktami i prośbą o współautorów.
Nie jest to powód, żeby listę odłożyć. Jest to powód, żeby czytać ją jak dobry podręcznik z poprzedniego wydania: rozdziały o architekturze, obsłudze błędów i testowaniu starzeją się wolno, konkretne nazwy bibliotek i numery wersji — szybko.
Dla polskiego czytelnika jest jeszcze jedna pułapka, wyraźniejsza niż wszystkie powyższe. Polskie tłumaczenie jest z ery Node 12. Plakietki w README.polish.md mówią „Last update — November 12 2019” i „Node 12.12.0”, punktów jest 85 zamiast 108, a plik był ostatnio ruszany w lipcu 2023 przy okazji poprawki w przykładzie kodu. Node 12 skończył wsparcie w 2022 roku. Czytaj wersję angielską — polska nie jest starszym wydaniem tej samej książki, tylko innym wydaniem sprzed sześciu lat.
Co z tej listy warto wziąć
Poniżej rzeczy, które naszym zdaniem uzasadniają lekturę — z komentarzem, bo sam punkt bez kontekstu bywa oczywistością.
Architektura: podział po domenach, nie po typach plików
Pierwszy punkt listy jest jednocześnie najważniejszy: korzeń projektu ma zawierać komponenty odpowiadające domenom biznesowym (zamowienia, uzytkownicy, platnosci), a nie katalogi controllers, services, models. Każdy komponent ma własne API, własną logikę i własną logiczną bazę. Nie wymaga to fizycznego rozdzielenia — monorepo wystarczy.
To rada, która przenosi się między językami bez zmian i którą w Laravelu widzieliśmy w praktyce po obu stronach. Projekt z app/Http/Controllers pełnym pięćdziesięciu klas różnych domen daje się utrzymywać do pewnego rozmiaru, a potem każda zmiana zaczyna dotykać wszystkiego. Podział po domenach, z warstwą wspólną wyciągniętą do osobnego katalogu bibliotek, jest tańszy do wprowadzenia na początku niż po trzech latach.
Punkt drugi domyka pierwszy: trzy warstwy w komponencie, a warstwa webowa nie wychodzi poza swoje granice — obiekty żądania i odpowiedzi nie wędrują do logiki domenowej. W praktyce oznacza to, że logikę da się wywołać z kolejki, z komendy CLI i z testu bez atrapy HTTP. To jest kryterium, po którym najszybciej sprawdzisz, czy warstwy w Twoim projekcie istnieją naprawdę.
Błędy: rozróżnienie, które zmienia projekt aplikacji
Sekcja o błędach jest najlepszą częścią listy i ma jedną myśl przewodnią: istnieją błędy operacyjne i katastrofalne, i trzeba je traktować inaczej. Nieudana walidacja, odmowa płatności, timeout zewnętrznego API to zdarzenia przewidziane — obsługuje się je i idzie dalej. Wyczerpana pamięć, niezłapane odrzucenie obietnicy w nieznanym miejscu, niespójny stan procesu to sytuacje, po których proces ma się zakończyć i pozwolić się zrestartować.
Z tego wynikają kolejne punkty i wszystkie są konkretne:
- obsługa błędów w jednym miejscu, nie w middleware — middleware nie łapie błędów z kolejek, zadań cyklicznych i konsumentów zdarzeń,
- rozszerzanie wbudowanego
Errorzamiast rzucania łańcuchów znaków i obiektów, żeby ślad stosu i typ były zachowane, - zawsze
awaitprzed zwróceniem obietnicy — inaczej ślad stosu urywa się w miejscu, w którym nic nie mówi (punkt oznaczony jako nowy), - subskrypcja zdarzenia
errorna emiterach i strumieniach — nieobsłużoneerrorna strumieniu kończy proces (również nowy punkt), - testowanie ścieżek błędów, nie tylko szczęśliwych,
- dokumentowanie błędów API w OpenAPI albo w schemacie GraphQL.
W Laravelu ten sam podział siedzi w Handler: metody report() i render() plus lista wyjątków, których nie raportujemy. Warto tę listę przejrzeć pod kątem pytania z listy Node'a: czy to, co tu jest, to naprawdę błędy operacyjne, czy po prostu wyciszyliśmy coś, co hałasowało.
Testy: trzy części nazwy i pięć możliwych wyników
Sekcja o testach ma cztery punkty warte natychmiastowego zastosowania, niezależnie od języka.
Nazwa testu z trzech części: co jest testowane, w jakich okolicznościach i jaki jest oczekiwany rezultat. Test o nazwie „dodawanie zamówienia” nie mówi nic; „dodanie zamówienia bez produktów zwraca 400” mówi wszystko, w tym to, czy błąd w tym teście jest regresją, czy zmianą wymagań.
Wzorzec AAA — przygotowanie, działanie, sprawdzenie, oddzielone wizualnie. Brzmi jak porada dla początkujących, ale to jest różnica między testem, który po awarii czytasz sekundę, i takim, który czytasz pięć minut.
Zacznij od testów komponentowych, czyli takich, które wchodzą przez API i sprawdzają wynik na zewnątrz, zamiast od testów jednostkowych każdej klasy. Przy ograniczonym budżecie na testy to one dają najwięcej pokrycia na godzinę pracy i nie rozsypują się przy refaktoryzacji.
Pięć możliwych wyników — sprawdzaj nie tylko odpowiedź, ale też stan bazy, wywołania zewnętrznych usług, kolejkę i logi błędów. Test, który sprawdza wyłącznie kod odpowiedzi, przejdzie w scenariuszu, w którym zamówienie zostało utworzone dwa razy, a wiadomość nie poszła.
Do tego dwa punkty czysto praktyczne: bez globalnych fikstur i seedów — dane tworzy każdy test u siebie, inaczej testy zależą od kolejności — i port losowany w testach, jawny na produkcji.
Produkcja i kontenery
Sekcja o wyjściu na produkcję jest najdłuższa i najbardziej mieszana pod względem świeżości, ale kilka punktów jest ponadczasowych: loguj na standardowe wyjście i nie decyduj w aplikacji, gdzie logi mają trafiać (o miejsce docelowe dba środowisko uruchomieniowe), bądź bezstanowy, przypisz identyfikator transakcji do każdej linii logu, blokuj wersje zależności i instaluj przez npm ci, a nie npm install. Ostatni punkt ma dokładny odpowiednik w naszym świecie: composer install --no-dev z zacommitowanym composer.lock, nigdy composer update na wdrożeniu.
Sekcja o Dockerze trzyma się nieźle, bo dotyczy rzeczy, które się nie zmieniły:
- budowanie wielostopniowe — narzędzia budowania nie wchodzą do obrazu produkcyjnego,
- uruchamianie procesu komendą
node, nienpm start— pośrednik przechwytuje sygnały i psuje zamykanie kontenera, .dockerignore, żeby do obrazu nie wjechało.envani.git,- limity pamięci ustawione w dwóch miejscach — w Dockerze i w silniku V8; sam limit kontenera powoduje, że proces jest zabijany, zamiast najpierw uruchomić czyszczenie pamięci,
- jawny tag obrazu bazowego zamiast
latest, - uruchamianie jako użytkownik bez uprawnień roota — w kontenerach domyślnie jest odwrotnie.
Ostatnie dwa punkty to dokładnie te same rzeczy, które sprawdzamy w obrazach PHP-owych. Podział na etapy, użytkownik bez roota, brak sekretów w argumentach budowania — język w środku nie ma tu znaczenia.
Bezpieczeństwo
Dwadzieścia siedem punktów, w większości nadal aktualnych, bo dotyczą klas błędów, nie konkretnych narzędzi: parametryzowane zapytania zamiast składania SQL-a z łańcuchów, walidacja wejścia schematem, nagłówki bezpieczeństwa w odpowiedzi, bcrypt albo scrypt do haseł, limit rozmiaru żądania, limit liczby żądań, unieważnianie tokenów JWT (bo sam JWT unieważnić się nie da), ostrożność z procesami potomnymi, ukrywanie szczegółów błędu przed klientem, brak eval, uwaga na wyrażenia regularne zdolne zablokować wątek.
Jeden punkt z tej sekcji jest specyficzny dla Node'a i wart zapamiętania: importuj moduły wbudowane z prefiksem node:. Zapis require('node:fs') zamiast require('fs') odbiera atakującemu możliwość podstawienia pakietu o nazwie modułu wbudowanego i jest dziś konwencją, nie ozdobą.
Czego na liście brakuje w 2026 roku
Tu jest właściwa wartość dodana do lektury, bo to są rzeczy, których lista nie powie, a które zmieniają decyzje projektowe. Wszystkie poniższe daty i statusy pochodzą z dokumentacji i harmonogramu wydań Node'a.
Numery wersji: plakietka jest o dwie linie z tyłu
Lista deklaruje aktualność dla Node 22.0.0. Stan na sierpień 2026:
- Node 20 (Iron) — wsparcie zakończone 30 kwietnia 2026. Jeśli coś u Ciebie jeszcze na tym stoi, to jest zaległość bezpieczeństwa,
- Node 22 (Jod) — w trybie utrzymaniowym od 21 października 2025, koniec wsparcia 30 kwietnia 2027,
- Node 24 (Krypton) — aktywne LTS od 28 października 2025, tryb utrzymaniowy od października 2026, koniec wsparcia w 2028,
- Node 26 — wydany 5 maja 2026, wchodzi w LTS 28 października 2026.
Czyli: punkt „używaj wydania LTS” jest słuszny, a liczba w plakietce wskazuje dziś linię utrzymaniową. Do nowego projektu bierze się 24, a przy planowaniu na czwarty kwartał warto już patrzeć na 26.
Wbudowany runner testów
Na całej liście nie ma ani jednego wystąpienia node:test. Tymczasem wbudowany runner testów jest stabilny od Node 20.0.0, uruchamiany flagą --test. Dla nowego serwisu oznacza to jedną zależność mniej i brak konfiguracji frameworka testowego na start.
Nie jest to argument za wyrzuceniem istniejącej konfiguracji — ekosystem wokół dojrzałych frameworków testowych nadal jest bogatszy. Jest to natomiast argument, żeby przy nowym mikroserwisie zadać pytanie, którego lista nie zadaje.
Konfiguracja ze pliku bez biblioteki
Punkt o konfiguracji świadomej środowiska, hierarchicznej i bezpiecznej jest dobry, ale powstał, gdy wczytanie .env wymagało pakietu. Dziś:
node --env-file=.env --env-file=.development.env index.js--env-file pojawiło się w wersji 20.6.0 i przestało być eksperymentalne w 24.10.0. Wartości wielolinijkowe działają od 21.7.0 i 20.12.0. Kolejne pliki nadpisują wcześniejsze, a zmienna ustawiona w środowisku ma pierwszeństwo nad tą z pliku. Brak pliku to błąd — jeśli ma być opcjonalny, jest --env-file-if-exists (od 22.9.0, stabilne również od 24.10.0). Do prostego przypadku biblioteka przestała być potrzebna.
Model uprawnień, o którym lista nie wie
Sekcja o bezpieczeństwie ma punkt o uruchamianiu bez roota i punkt o unikaniu eval, ale nie ma nic o modelu uprawnień Node'a. Flaga --permission pojawiła się w wersji 20.0.0, a model został uznany za stabilny w 22.13.0 i 23.5.0. Pozwala odebrać procesowi prawo czytania i zapisu w systemie plików, uruchamiania procesów potomnych i ładowania natywnych dodatków — poza tym, co jawnie dopuścisz.
Dla usługi, która przyjmuje dane z zewnątrz i nie ma powodu pisać na dysku, to jest dokładnie ta warstwa obrony, której brakuje między „uruchom jako nie-root” i „nie używaj eval”. Przy okazji: to samo rozwiązanie stoi za bezpieczeństwem buildów w Hugo, o czym pisaliśmy przy okazji jego pipeline'u assetów — narzędzia Node'owe są tam uruchamiane właśnie z --permission.
Drobiazgi, które zdążyły dojrzeć
node --run skrypt(od 22.0.0) uruchamia skrypt zpackage.jsonbez pośrednictwa menedżera pakietów — mniej narzutu przy każdym wywołaniu, co przy zadaniach w CI sumuje się w mierzalne sekundy,node:sqlite— wbudowany moduł SQLite, od 24.15.0 w statusie kandydata do wydania. Do fikstur testowych i lokalnych narzędzi bywa wystarczający bez żadnej zależności,- brak
worker_threadsna liście jest zaskakujący w dokumencie, który ma punkt „nie blokuj pętli zdarzeń”. Wątki robocze są właściwą odpowiedzią na obliczenia blokujące i to jest luka wynikająca ze szkicowego stanu sekcji o wydajności, - brak OpenTelemetry — lista mówi o produktach APM, nie o standardzie, którym się dziś do nich podłącza. Punkt o identyfikatorze transakcji w logach zapowiada rozproszone śledzenie, ale zatrzymuje się przed nim.
Jak to czytamy u nas
Nie jesteśmy zespołem Node'owym — nasz trzon to PHP i Laravel — ale usług w Node mamy dość, żeby taka lista miała u nas konkretne zastosowanie. Dwa, żeby być dokładnym.
Pierwsze: lista kontrolna do code review. Nie do czytania od deski do deski, tylko do sięgnięcia, gdy patrzymy na kod usługi w Node. Osiem sekcji układa się w naturalny porządek przeglądu — architektura, błędy, testy, produkcja, bezpieczeństwo, kontener — a każdy punkt ma sformułowany kontrprzykład, który da się wkleić do komentarza w merge requeście jako uzasadnienie. To jest coś, czego samodzielne wypisanie zajęłoby tygodnie.
Drugie: materiał do onboardingu, z zastrzeżeniem. Zastrzeżenie brzmi: przeczytaj wersję angielską, plakietki ignoruj, wersje sprawdź w dokumentacji Node'a. Bez tego wprowadzenie do zespołu zawiera zdania nieaktualne o dwa lata, a to gorzej niż ich brak.
Warto też zauważyć, ile z tej listy w ogóle nie dotyczy Node'a. Rozróżnienie błędów operacyjnych i katastrofalnych, trzyczęściowe nazwy testów, wzorzec AAA, podział po domenach, logowanie na standardowe wyjście, bezstanowość, identyfikator transakcji, budowanie wielostopniowe, brak sekretów w argumentach budowania — to wszystko jest prawdą w PHP, w Go i w Pythonie. Specyficznych dla Node'a jest może dwadzieścia punktów: pętla zdarzeń, wykorzystanie rdzeni przez klastrowanie, limity pamięci V8, prefiks node:, ReDoS na jednym wątku. Reszta to inżynieria oprogramowania z przykładami w JavaScripcie — i dlatego ta lista jest warta czasu również wtedy, gdy Node jest u Ciebie usługą poboczną.
Podsumowanie
Node.js Best Practices to najlepsze kilka godzin, jakie można zainwestować w usystematyzowanie wiedzy o backendzie w Node — pod warunkiem, że czyta się to z aktualnym harmonogramem wydań Node'a w drugiej karcie przeglądarki.
Co dostajesz:
- 108 punktów w ośmiu sekcjach, każdy ze streszczeniem, kontrprzykładem i odnośnikiem do rozwinięcia — łącznie 630 plików z omówieniami,
- tagi
#newi#updated, dzięki którym powtórna lektura kosztuje kilkanaście minut, - sekcję o obsłudze błędów, która jest lepsza niż większość rozdziałów w książkach na ten temat, i sekcję o testach, którą da się zastosować w każdym języku,
- piętnaście punktów o Dockerze przenoszących się na obrazy PHP-owe niemal jeden do jednego,
- dziesięć tłumaczeń — z których polskie jest z 2019 roku i nie należy z niego korzystać.
O czym pamiętać: „edycja 2026” to commit zmieniający dwie linie z czerwca 2026, plakietki mówią o styczniu 2023 i Node 22, sekcja o wydajności od lat jest szkicem z dwoma punktami, a liczba punktów podana w dokumencie w trzech miejscach różni się w każdym z nich. Brakuje wbudowanego runnera testów (stabilny od Node 20), wczytywania .env przez --env-file (nieeksperymentalne od 24.10.0), modelu uprawnień (stabilny od 22.13.0), wątków roboczych i OpenTelemetry.
Licencja: CC-BY-SA 4.0 — i to jest jedyny punkt, w którym trzeba uważać przy użyciu komercyjnym, bo licencja treści działa inaczej niż licencja kodu. Same zalecenia są wiedzą i nikt nie licencjonuje faktu, że warto parametryzować zapytania. Ale tekst listy jest objęty warunkiem „na tych samych warunkach”: skopiowanie albo przetłumaczenie istotnej części do wewnętrznej dokumentacji, którą potem udostępniasz dalej, tworzy utwór zależny — z obowiązkiem podania autorstwa i wypuszczenia go na tej samej licencji. Bezpieczna droga jest zwyczajna i wystarczająca: piszesz własnymi słowami, podajesz odnośnik do źródła i nie wklejasz akapitów. Tak też powstał ten wpis.