Головна / Статті / Дев’ять корисних пакетів Node.js, які варто додати перед початком програмування

Дев’ять корисних пакетів Node.js, які варто додати перед початком програмування

Дізнайтеся, як невеликий набір пакетів Node.js — що включає налаштування середовища, перевірку даних, скриптування, обробку процесів та логування — може допомогти виявити поширені помилки на ранній стадії.

1381 слів

Кожен новий проект Node схильний дотримуватися однакової схеми: порожня директорія, єдиний файл index.js, а також наївне переконання, що стандартна бібліотека покриє більшу частину потреб. Через кілька днів ви починаєте вручну писати власний цикл повторних спроб, обробляти дати за допомогою регулярних виразів та створювати ще один, трохи інший, лоадер для файлів .env.

У певний момент стає доцільно припинити заново винаходити велосипеди та скористатися невеликим, послідовним набором інструментів перед тим, як писати справжню логіку. Жоден з цих пакетів не є привабливим з точки зору функціоналу. Кожен з них тихо усуває певну категорію помилок, які часто вважаються неминучою ціною розробки програмного забезпечення. Ось дев’ять пакетів, які варто встановити на початковому етапі будь-якого проекту.

1. dotenv

Якщо ви коли-небудь випадково додавали API-ключ у git-репозиторій, ви вже розумієте переваги цього пакету. dotenv завантажує пари «ключ-значення» з файлу .env у process.env, зберігаючи конфіденційну інформацію у файлі, який не підлягає контролю версій, замість того щоб вбудовувати її безпосередньо у ваш код.

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

Це мінімальний пакет із вузьким функціоналом, але він робить різницю між тим, коли всі налаштування знаходяться в одному передбачуваному місці, та коли вони розкидані по кількох файлах, а інформація ховається у старому чаті.

2. zod

Перевірку схеми під час виконання програми легко ігнорувати, якщо здається, що кілька операторів if достатні для перевірки. Однак ця впевненість зазвичай зникає вже при першій спробі надсилання некоректного запиту, який пройшов ці перевірки та потрапив у продакшн.

zod дозволяє визначити форму даних один раз та отримати з неї як валідатор під час виконання, так і відповідний тип 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);
});

Найбільша перевага проявляється на межах вашого додатку: вхідні дані API, змінні середовища, файли конфігурації та будь-де ще, де дані потрапляють у вашу систему. Валідація в цих точках входу означає, що решта вашого коду може з упевненістю припускати, що отримані дані мають очікувану форму.

3. tsx

Кожен, хто працює з TypeScript, ймовірно стикався з надлишковими витратами на виконання окремого скрипту: компіляція за допомогою tsc перед запуском результату або налаштування ts-node та очікування його часу запуску. tsx повністю усуває ці перешкоди, виконуючи файли .ts безпосередньо, без необхідності кроку компіляції та конфігурації.

tsx scripts/migrate-users.ts

Він не призначений для заміни процесу побудови продакшн-версії. Він створений для десятків невеликих скриптів, які з часом накопичуються в кожному кодовому базі, таких як одноразові міграції, скрипти для ініціалізаційних даних чи швидкі перевірки даних. Ці скрипти не вимагають повної налаштованості середовища побудови; їм просто потрібно виконуватися майже миттєво, щоб ви не втрачали часу у очікуванні компілятора.

4. execa

Вбудований модуль Node child_process виконує цю роботу, але його правильне використання означає необхідність щоразу вручну керувати stdout, stderr, кодами завершення та помилками. execa об’єднує все це в інтерфейс, який працює саме так, як ви цього очікуєте:

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

Помилки викидаються так, як і слід, замість того, щоб працювати беззвучно; результат виводу автоматично обрізається, а підтримка async/await є вбудованою, без необхідності створювати власний обгорток для promise навколо spawn. Для тих, хто створює інструменти CLI або скрипти, які викликають інші програми, цей єдиний пакет усуває цілу категорію помилок, коли щось тихо нічого не робить, і доводиться здогадуватися про причини.

5. p-retry

Мережі є ненадійними. API встановлюють обмеження на швидкість запитів. Підключення до баз даних іноді втрачаються з причин, які ніколи не пояснюються повністю. p-retry обгортає будь-яку асинхронну функцію логікою повторних спроб та експоненційним затримуванням, тож один нестабільний виклик не призведе до зупинки всього процесу:

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

Часто доводиться вручну писати таку логіку для кожного проекту, що зазвичай супроводжується помилками, а також часто без належного затримування спроб, що може ще більше навантажити проблемний API через швидкі повторні спроби. Цей пакет правильно вирішує цю проблему лише за кілька рядків коду та допоміг уникнути повного збою понад однієї інтеграції під час звичайного переривання роботи стороннього сервісу.

6. day.js

Робота з датами у звичайному JavaScript є надзвичайно складною, а бібліотека moment.js, яку зазвичай використовували, є важкою та більше не розвивається. day.js пропонує подібно зручний API, але з значно меншим об’ємом:

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

Форматування дат, їх порівняння, додавання чи віднімання проміжків часу, а також обробка складних рядків з датами стають простими, що означає, що ви перестаєте стикатися з тонкими помилками, пов’язаними з різницею в один день, які виникають при ручних обчисленнях.

7. pino

console.log підходить для невеликих скриптів, але коли ви запускаєте сервіс у продакшені, який генерує тисячі записів журналу на хвилину, вам потрібен інструмент для фільтрації та пошуку. pino формує структуровані JSON-записи журналу з такою швидкістю, що це майже не впливає на продуктивність вашого додатку:

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

Оскільки результат має структуровану форму, будь-який інструмент, який ви використовуєте для його обробки — Datadog, стек ELK чи навіть просте використання grep для аналізу файлу — може належним чином його парсувати та фільтрувати. Це краще, ніж перегортати сторінки простого тексту у пошуках потрібного рядка, коли вже є криза.

8. cheerio

Іноді все, що потрібно, — це витягнути фрагмент структурованих даних з HTML-сторінки, і запуск повного безголового браузера здається надмірним для завдання, яке насправді полягає лише у „знайти цей елемент та прочитати його текст“. cheerio надає API, схожий на jQuery, для парсингу та запитів до HTML з боку сервера, без необхідності запуску справжнього браузера:

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

Він не виконує JavaScript на сторінці, тому не може замінити інструменти на кшталт Playwright, коли контент відображається на клієнтській стороні. Але для збирання інформації зі статичної маркувальної мови, обробки контенту у форматі фідів чи вилучення даних зі сторінок, якими ви керуєте, він значно швидший та легший, ніж запуск окремої інстанції браузера.

9. pm2

Запуск вашого додатку за допомогою node index.js є прийнятним, поки він не зупиниться посеред ночі, і ніхто його не перезапустить. pm2 підтримує роботу вашого процесу, автоматично перезапускає його після збою та забезпечує базовий моніторинг та обробку журналів, усе це без необхідності використання повноцінної платформи для оркестрації контейнерів.

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

Для багатьох невеликих та середніх систем це справді вся необхідна наглядова функція. Вона не замінить Kubernetes у випадку керування великою розподіленою системою, але на одному сервері з кількома процесами Node це різниця між необхідністю щоразу підключатися через SSH, коли щось зупиняється, та відсутністю цієї проблеми зовсім.

Справжня суть

Жоден з цих дев’яти пакетів сам по собі не є особливо привабливим, і саме це є головним висновком. Кожен з них бере на себе частину логіки, яку інакше довелося б писати самостійно, зазвичай роблячи при цьому невеликі помилки вже на першій спробі, а потім змушений бути підтримуваним нескінченно. Використання таких пакетів — це не спосіб скоротити шлях. Це спосіб спрямувати ваші обмежені ресурси на ті частини додатку, які справді потрібно створювати власноруч, замість того щоб знову реалізовувати та виправляти логіку циклів спроб для п’ятого проекту поспіль.

Пов’язана література

  • Проєктування API Node.js у шарах: від „товстих“ контролерів до чистої архітектури — Дізнайтеся, як переписати API Node.js на основі шарів контролерів, сервісів та доступу до даних, щоб усунути складну бізнес-логіку, непослідовні помилки та проблеми з масштабуванням.