Главная / Статьи / Девять полезных пакетов 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 может справиться с этой задачей, но для его правильного использования необходимо каждый раз вручную обрабатывать стандартный вывод, ошибки вывода, коды завершения и возникающие проблемы. execa объединяет всё это в интерфейс, который работает именно так, как вы ожидаете:

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

Ошибки выбрасываются так, как и должно быть, вместо того чтобы происходить бесшумно; вывод автоматически обрезается, а поддержка async/await встроена из коробки, без необходимости писать собственный оберток для promise вокруг spawn. Для тех, кто создаёт инструменты командной строки или скрипты, вызывающие другие программы, этот пакет устраняет целый класс ошибок, при которых что-то бесшумно ничего не делает, и вам приходится гадать почему.

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 с использованием слоев контроллеров, сервисов и доступа к данным для устранения запутанной бизнес-логики, неоднородных ошибок и проблем с масштабированием.