首页 / 文章 / 在开始编码之前值得添加的九个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 的支持,无需为 spawn 编写自定义的 Promise 封装。对于那些需要构建能够调用其他程序的 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 提供了类似 jQuery 的 API,可在服务器端解析和查询 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 登录的麻烦,从根源上解决该问题。

核心要点

这九个模块单独来看都没有什么特别出色的地方,而这恰恰是关键所在。每个模块都能替代你原本需要自己编写的那部分简单逻辑,通常第一次实现时就会出些小错,之后还得持续维护。使用这些模块并非为了走捷径,而是将有限的精力集中在真正需要你自己构建的应用部分上,而不是反复为第五个项目实现并调试重试循环。

相关阅读

  • 分层式 Node.js API 设计:从臃肿的控制器到整洁架构 — 了解如何将 Node.js API 重构为控制器、服务层和数据访问层,从而解决复杂的业务逻辑、不一致的错误以及扩展难题。