Nueve paquetes útiles de Node.js que vale la pena agregar antes de comenzar a programar
Aprenda cómo un pequeño conjunto de paquetes de Node.js, que abarcan la configuración del entorno, la validación, la creación de scripts, el manejo de procesos y el registro de logs, puede eliminar los errores comunes desde temprano.
Cada nuevo proyecto Node tiende a seguir el mismo esquema: un directorio vacío, un único archivo index.js, y la creencia ingenua de que la biblioteca estándar cubrirá la mayor parte de lo que se necesita. Después de unos días, ya estás escribiendo tú mismo un bucle de intentos manualmente, analizando fechas con expresiones regulares y creando otro cargador de .env ligeramente diferente.
En algún momento tiene sentido dejar de reinventar la rueda y, en su lugar, recurrir a una herramienta pequeña y coherente antes de escribir cualquier lógica real. Ninguno de estos paquetes es llamativo; cada uno elimina silenciosamente una categoría de errores que a menudo se considera un costo inevitable al desarrollar software. A continuación se presentan nueve que vale la pena instalar desde el principio en cualquier proyecto.
1. dotenv
Si alguna vez has enviado por error una clave API a un repositorio Git, ya entiendes la utilidad de este paquete. dotenv carga pares clave-valor desde un archivo .env en process.env, guardando los datos confidenciales en un archivo que queda fuera del control de versiones en lugar de estar incrustados directamente en tu código.
// .env
DATABASE_URL=postgres://localhost:5432/mydb
STRIPE_SECRET_KEY=sk_test_...
// index.js
import "dotenv/config";
const db = connect(process.env.DATABASE_URL);
Es un paquete mínimo con una función específica, pero marca la diferencia entre tener toda tu configuración en un lugar predecible y dispersarla en varios archivos o en un mensaje enterrado en una conversación antigua.
2. zod
Es fácil descartar la validación de esquemas en tiempo de ejecución cuando un par de instrucciones if parecen ser suficientes. Esa confianza suele desaparecer en cuanto una solicitud mal formada logra pasar esas verificaciones y llegar a producción.
zod le permite definir la estructura de los datos una sola vez y derivar tanto un validador en tiempo de ejecución como un tipo correspondiente en 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);
});
La mayor ventaja se manifiesta en los puntos de entrada de su aplicación: cargas de API entrantes, variables de entorno, archivos de configuración y cualquier otro lugar por donde los datos ingresen a su sistema. Al validar en estos puntos de entrada, el resto del código puede asumir con seguridad que los datos recibidos tienen la estructura esperada.
3. tsx
Cualquiera que escriba en TypeScript probablemente haya tenido que lidiar con la complejidad de ejecutar un script único: compilar con tsc antes de ejecutar el resultado, o configurar ts-node y esperar a que se inicie. tsx elimina por completo esta complejidad al ejecutar directamente los archivos .ts, sin necesidad de pasos de compilación ni configuración alguna.
tsx scripts/migrate-users.ts
No está diseñado para reemplazar un pipeline de compilación en entornos de producción. Fue creado para las decenas de scripts pequeños que cada base de código acumula con el tiempo, como migraciones puntuales, scripts de inicialización o verificaciones rápidas de datos. Estos no requieren una configuración completa de compilación; solo necesitan ejecutarse casi al instante para que no se pierda el impulso esperando a un compilador.
4. execa
El módulo integrado de Node, child_process, cumple con esta función, pero usarlo correctamente implica manejar manualmente la salida estándar, la salida de error, los códigos de finalización y los errores en cada ocasión. execa agrupa todo eso en una interfaz que funciona de la manera que uno esperaría naturalmente:
import { execa } from "execa";
const { stdout } = await execa("git", ["rev-parse", "--short", "HEAD"]);
console.log(`Current commit: ${stdout}`);
Los errores se lanzan como deberían, en lugar de ocultarse silenciosamente; la salida se procesa automáticamente y el soporte para async/await viene integrado, sin necesidad de crear un wrapper personalizado con promesas alrededor de spawn. Para quienes desarrollan herramientas CLI o scripts que interactúan con otros programas, este único paquete elimina toda una categoría de errores en los que algo no funciona silenciosamente y uno debe adivinar por qué.
5. p-retry
Las redes son poco fiables. Las APIs imponen límites de velocidad. Las conexiones a bases de datos a veces se interrumpen por razones que nunca se explican completamente. p-retry envuelve cualquier función asíncrona con lógica de reintentos y retroceso exponencial, de modo que una llamada problemática no arruine todo el proceso:
import pRetry from "p-retry";
const data = await pRetry(() => fetchFromFlakyApi(url), {
retries: 5,
onFailedAttempt: (error) => {
console.log(`Attempt ${error.attemptNumber} failed. Retrying...`);
},
});
Es común terminar escribiendo este tipo de lógica manualmente para cada proyecto, generalmente con errores y a menudo sin un mecanismo adecuado de retroceso, lo que puede causar aún más estrés en una API ya con problemas debido a los intentos repetidos rápidamente. Este paquete lo maneja correctamente en solo unas pocas líneas, y ha evitado que más de una integración falle por completo durante interrupciones rutinarias en un servicio de terceros.
6. day.js
Trabajar con fechas en JavaScript puro es notoriamente complicado, y moment.js, la biblioteca a la que la mayoría de las personas solían recurrir por defecto, es pesada y ya no cuenta con desarrollo activo. day.js ofrece una API igualmente práctica, pero con un tamaño mucho menor:
import dayjs from "dayjs";
const deadline = dayjs().add(3, "day").format("YYYY-MM-DD");
const isOverdue = dayjs(invoice.dueDate).isBefore(dayjs());
El formateo de fechas, su comparación, la adición o sustracción de intervalos de tiempo y el análisis de cadenas de fechas complejas se vuelven sencillos, lo que significa que deja de surgir aquellos errores sutiles por error de un día causados por realizar estos cálculos manualmente.
7. pino
console.log funciona bien para scripts pequeños, pero una vez que esté ejecutando un servicio en producción que genera miles de registros de registro por minuto, necesita algo con lo que pueda filtrar y buscar realmente. pino genera registros de JSON estructurados a una velocidad que prácticamente no afecta de manera medible a su aplicación:
import pino from "pino";
const logger = pino();
logger.info({ userId: user.id, action: "checkout" }, "Order placed");
Dado que la salida está estructurada, cualquier herramienta que se utilice para consumirla —Datadog, una pila ELK o incluso simplemente ejecutar grep en un archivo más tarde— puede analizarla y filtrarla adecuadamente. Eso es mejor que desplazarse por páginas de texto plano tratando de encontrar la línea relevante mientras ocurre un problema crítico.
8. cheerio
A veces lo único que se necesita es extraer un fragmento de datos estructurados de una página HTML, y ejecutar un navegador sin interfaz completa parece un exceso enorme para una tarea que en realidad consiste simplemente en “localizar este elemento y leer su texto”. cheerio ofrece una API similar a jQuery para analizar y consultar HTML desde el lado del servidor, sin ninguno de los costos que implica iniciar un navegador real:
import * as cheerio from "cheerio";
const $ = cheerio.load(html);
const titles = $("h2.product-title")
.map((_, el) => $(el).text().trim())
.get();
No ejecuta JavaScript en la página, por lo que no puede sustituir a una herramienta como Playwright cuando el contenido se renderiza en el lado del cliente. Pero para escanear marcado estático, trabajar con contenido similar a feeds o extraer valores de páginas que controlas, es mucho más rápido y ligero que iniciar una instancia del navegador.
9. pm2
Ejecutar tu aplicación con node index.js está bien hasta que se cae en medio de la noche y nada la vuelve a iniciar. pm2 mantiene tu proceso en ejecución, lo reinicia automáticamente después de una caída y ofrece monitoreo básico y gestión de registros, todo sin necesidad de una plataforma completa de orquestación de contenedores.
pm2 start index.js --name my-api
pm2 logs my-api
pm2 restart my-api
Para muchas implementaciones pequeñas y de tamaño medio, esta es realmente toda la supervisión del proceso que necesitarás. No reemplazará a Kubernetes si estás gestionando un sistema distribuido grande, pero en un único servidor que ejecuta un par de procesos Node, marca la diferencia entre tener que conectarte vía SSH cada vez que algo falla y no tener ese problema desde el principio.
El punto clave
Ninguno de estos nueve paquetes es especialmente llamativo por sí solo, y esa es en realidad la idea principal. Cada uno se encarga de una pequeña parte de la lógica que de otro modo tendrías que escribir tú mismo, generalmente cometiendo errores leves en el primer intento y luego viéndote obligado a mantenerla indefinidamente. Recurrir a ellos no significa tomar atajos, sino dirigir tu limitado enfoque hacia las partes de la aplicación que realmente puedes desarrollar tú, en lugar de volver a implementar y depurar un bucle de intentos repetidos para el quinto proyecto que ejecutas.
Lecturas relacionadas
- Características de Node.js 26 que reemplazan silenciosamente años de soluciones temporales — Un recorrido por la API Temporal de Node.js 26, la ejecución nativa de TypeScript, los ayudantes de caché y otras adiciones que eliminan soluciones temporales que han existido durante mucho tiempo.