Strona główna / Artykuły / Dziewięć przydatnych pakietów Node.js, które warto dodać przed rozpoczęciem programowania

Dziewięć przydatnych pakietów Node.js, które warto dodać przed rozpoczęciem programowania

Dowiedz się, jak niewielka grupa pakietów Node.js – obejmująca konfigurację środowiska, walidację, skrypty, obsługę procesów oraz logowanie – może wczesnie wyeliminować powszechne błędy.

1381 słów

Każdy nowy projekt Node ma tendencję do stosowania tego samego schematu: pusta katalog, pojedynczy plik index.js oraz naiwna przekonanie, że standardowa biblioteka pokryje większość potrzeb. Po kilku dniach zaczynasz ręcznie pisać własne pętle ponawiania prób, parsować daty za pomocą wyrażeń regularnych i tworzyć kolejny, nieco inny loader plików .env.

W pewnym momencie ma sens przestać wymyślać od nowa te rozwiązania i zamiast tego skorzystać z małego, spójnego zestawu narzędzi przed napisaniem jakiejkolwiek rzeczywistej logiki. Żaden z tych pakietów nie jest efektowny. Każdy z nich cicho eliminuje określoną kategorię błędów, które często traktowane są jako nieunikniona cena tworzenia oprogramowania. Oto dziewięć pakietów, które warto zainstalować na wczesnym etapie każdego projektu.

1. dotenv

Jeśli kiedykolwiek przez pomyłkę umieściłeś klucz API w repozytorium git, już rozumiesz zalety tego pakietu. dotenv ładuje pary klucz-wartość z pliku .env do process.env, przechowując tajne dane w pliku, który nie podlega kontroli wersji, zamiast mieć je bezpośrednio w kodzie.

// .env
DATABASE_URL=postgres://localhost:5432/mydb
STRIPE_SECRET_KEY=sk_test_...
// index.js
import "dotenv/config";
const db = connect(process.env.DATABASE_URL);

To minimalny pakiet o wąskim zakresie zadań, ale sprawia różnicę pomiędzy przechowywaniem całej konfiguracji w jednym przewidywalnym miejscu a jej rozproszeniem między wieloma plikami oraz wiadomością ukrytą w starym temacie czatu.

2. zod

Walidację schematu w czasie wykonywania łatwo zaniedbać, gdy kilka instrukcji if wydaje się wystarczających. Ta pewność siebie zwykle znika po raz pierwszy, gdy nieprawidłowy żądanie przechodzi przez te kontrole i trafia do środowiska produkcyjnego.

zod umożliwia określenie struktury danych raz i na tej podstawie tworzenie zarówno walidatora w czasie wykonywania, jak i odpowiadającego mu typu TypeScript:

import { z } from "zod";
const CreateUserSchema = z.object({
  email: z.string().email(),
  age: z.number().min(13),
});type CreateUser = z.infer<typeof CreateUserSchema>;app.post("/users", (req, res) => {
  const result = CreateUserSchema.safeParse(req.body);
  if (!result.success) {
    return res.status(400).json({ error: result.error.flatten() });
  }
  // result.data is now typed and validated
  createUser(result.data);
});

Największa korzyść jest widoczna w punktach wejścia do aplikacji: przychodzące dane z API, zmienne środowiskowe, pliki konfiguracyjne oraz wszędzie indziej, skąd dane trafiają do systemu. Walidacja w tych punktach wejścia oznacza, że reszta kodu może bezpiecznie zakładać, iż otrzymywane dane mają oczekiwaną strukturę.

3. tsx

Każdy, kto pracuje z TypeScriptem, zapewne miał do czynienia z kosztami związanymi z wykonywaniem pojedynczego skryptu: kompilowaniem za pomocą tsc przed uruchomieniem wyniku lub konfigurowaniem ts-node i czekaniem na jego uruchomienie. tsx całkowicie eliminuje te problemy, ponieważ pozwala na bezpośrednie wykonywanie plików .ts bez konieczności etapu budowania i bez żadnej konfiguracji.

tsx scripts/migrate-users.ts

Nie został stworzony w celu zastąpienia pełnego procesu budowania oprogramowania w środowisku produkcyjnym. Jest przeznaczony dla dziesiątek małych skryptów, które z czasem gromadzą się w każdym kodzie źródłowym – takich jak jednorazowe migracje, skrypty inicjalizacyjne czy szybkie sprawdzenia danych. Nie wymagają one kompleksowej konfiguracji procesu budowania; wystarczy, że będą wykonywane niemal natychmiast, aby nie tracić czasu na czekanie na kompilatora.

4. execa

Wbudowany w Node moduł child_process sprosta temu zadaniu, ale jego prawidłowe używanie oznacza konieczność ręcznego obsługi standardowego wyjścia, błędów standardowych, kodów zakończenia oraz błędów za każdym razem. execa łączy to wszystko w interfejs, który funkcjonuje tak, jak można by się naturalnie spodziewać:

import { execa } from "execa";
const { stdout } = await execa("git", ["rev-parse", "--short", "HEAD"]);
console.log(`Current commit: ${stdout}`);

Błędy są rzucone w sposób właściwy, zamiast powodować ciche awarie; wyniki są automatycznie przycinane, a obsługa async/await jest wbudowana, bez konieczności pisania własnego otulacza obietnic wokół spawn. Dla wszystkich, którzy tworzą narzędzia CLI lub skrypty łączące się z innymi programami, ten jeden pakiet eliminuje całą klasę błędów, w których coś po cichu nic nie robi, a trzeba zgadywać dlaczego.

5. p-retry

Sieci są niewiarygodne. API narzucają limity szybkości. Połączenia z bazą danych czasami się rozrywają z powodów, które nigdy nie są w pełni wyjaśnione. p-retry otacza każdą funkcję asynchroniczną logiką ponawiania prób oraz eksponencjalnym opóźnieniem, dzięki czemu jedna niestabilna próba nie powoduje awarii całego procesu:

import pRetry from "p-retry";
const data = await pRetry(() => fetchFromFlakyApi(url), {
  retries: 5,
  onFailedAttempt: (error) => {
    console.log(`Attempt ${error.attemptNumber} failed. Retrying...`);
  },
});

Często zdarza się, że dla każdego projektu trzeba ręcznie pisać taką logikę, co zwykle skutkuje błędami i często brakiem odpowiedniego mechanizmu odroczenia prób, co może jeszcze bardziej obciążyć problematyczną API poprzez szybkie powtórzenia prób. Ten pakiet radzi sobie z tym poprawnie w zaledwie kilku liniach kodu i zapobiegł awarii wielu integracji podczas rutynowych przerw w działaniu usług dostawców zewnętrznych.

6. day.js

Praca z datami w zwykłym JavaScript jest niezwykle uciążliwa, a moment.js, biblioteka, do której najczęściej się uciekało, jest ciężka i nie jest już rozwijana aktywnie. day.js oferuje podobnie wygodny interfejs API, ale przy znacznie mniejszym obciążeniu systemu:

import dayjs from "dayjs";
const deadline = dayjs().add(3, "day").format("YYYY-MM-DD");
const isOverdue = dayjs(invoice.dueDate).isBefore(dayjs());

Formatowanie dat, porównywanie ich, dodawanie lub odejmowanie okresów czasu oraz analiza nieuporządkowanych ciągów znaków reprezentujących daty stają się proste, co oznacza, że przestajesz napotykać subtelne błędy wynikające z ręcznego wykonywania tych obliczeń.

7. pino

console.log sprawdza się dobrze w małych skryptach, ale gdy uruchamiasz usługę produkcyjną wytwarzającą tysiące zapisów dziennie, potrzebujesz narzędzia umożliwiającego filtrowanie i wyszukiwanie. pino wytwarza ustrukturyzowane logi w formacie JSON z tak dużą szybkością, że praktycznie nie ma to wpływu na wydajność twojej aplikacji:

import pino from "pino";
const logger = pino();
logger.info({ userId: user.id, action: "checkout" }, "Order placed");

Ponieważ wynik jest zstrukturyzowany, niezależnie od narzędzia używanego do jego przetwarzania – Datadog, stek ELK czy nawet prosty grep w pliku – można go prawidłowo analizować i filtrować. To o wiele lepsze niż przeglądanie stron z zwykłym tekstem w poszukiwaniu odpowiedniej linii, gdy sytuacja jest krytyczna.

8. cheerio

Czasami wystarczy wydobyć fragment zstrukturyzowanych danych z strony HTML, a uruchamianie pełnego bezekranowego przeglądarki wydaje się nadmiernym rozwiązaniem dla zadania, które polega po prostu na „znalezieniu tego elementu i odczytaniu jego tekstu”. cheerio oferuje API podobne do jQuery do analizy i wyszukiwania w HTML po stronie serwera, bez konieczności uruchamiania rzeczywistej przeglądarki:

import * as cheerio from "cheerio";
const $ = cheerio.load(html);
const titles = $("h2.product-title")
  .map((_, el) => $(el).text().trim())
  .get();

Nie wykonywa on JavaScript na stronie, więc nie może zastąpić narzędzi takiego jak Playwright, gdy treść jest renderowana po stronie klienta. Jednak przy scrapingu statycznej marki, przetwarzaniu treści w formie feedów lub wydobywaniu wartości z stron, które kontrolujesz, jest znacznie szybszy i lżejszy niż uruchamianie instancji przeglądarki.

9. pm2

Uruchamianie aplikacji za pomocą node index.js jest w porządku, dopóki nie zatrzyma się ona w środku nocy i nic jej nie restartuje. pm2 utrzymuje twój proces w działaniu, automatycznie go restartuje po awarii oraz zapewnia podstawową obsługę monitoringu i logów, wszystko to bez konieczności używania pełnej platformy do orkiestracji kontenerów.

pm2 start index.js --name my-api
pm2 logs my-api
pm2 restart my-api

Dla wielu małych i średnich implementacji to naprawdę cała nadzór procesów, jakiego kiedykolwiek będziesz potrzebował. Nie zastąpi ono Kubernetesa, jeśli zarządzasz dużym systemem rozproszonym, ale na pojedynczym serwerze z kilkoma procesami Node oznacza to różnicę między koniecznością logowania się przez SSH za każdym razem, gdy coś przestaje działać, a brakiem tego problemu od samego początku.

Rzeczywisty cel

Literatura pokrewna

  • Projektowanie warstwowe API w Node.js: Od rozbudowanych kontrolerów do czystej architektury — Dowiedz się, jak przeredagować API w Node.js na warstwy kontrolerów, usług i dostępu do danych, aby rozwiązać problemy z skomplikowaną logiką biznesową, niejednolitymi błędami oraz trudnościami w skalowaniu.