Como desarrolladores de backend, todos hemos necesitado automatizar tareas: limpiar una base de datos, enviar reportes diarios o ejecutar un script de mantenimiento.
La opción clásica suele ser node-cron, pero ¿qué pasa cuando cada kilobyte cuenta y quieres una solución moderna y sin una sola dependencia para la programación de tareas en Node.js?
La semana pasada me topé con Croner, un programador de tareas para JavaScript y TypeScript que me sorprendió por su simplicidad y potencia. En esta guía, te mostraré por qué se está convirtiendo en mi opción preferida para proyectos donde el rendimiento y la ligereza son críticos, siendo una excelente alternativa a node-cron.
Vamos a ver cómo puedes implementarlo en minutos.
¿Qué es Croner?
Croner es un programador de tareas para JavaScript y TypeScript sin dependencias. Permite ejecutar funciones o scripts en momentos específicos usando sintaxis CRON. Su diseño ultraligero lo hace ideal para proyectos Node.js donde el rendimiento y el bajo tamaño de paquete son críticos, siendo una alternativa moderna a node-cron.
Su característica principal es la ausencia total de dependencias, como puedes verificar en su página del paquete en NPM.
Esto significa que no incluye paquetes innecesarios en tiempo de ejecución, lo que resulta en un tamaño de paquete (bundle size) extremadamente reducido, confirmado por herramientas como Bundlephobia.
Además, está publicado en JSR, lo que facilita su distribución moderna y su uso en entornos como Bun y, en ciertos escenarios, en el navegador. Un caso de uso razonable en frontend es la implementación de sondeos periódicos (polling).
Ejemplo de cron job en Node.js con Croner
import { Cron } from "croner";
console.log("Demostración de Croner iniciada. Esperando la próxima ejecución...");
// Tarea principal que se ejecuta cada 5 segundos
const mainJob = new Cron("*/5 * * * * *", () => {
console.log("###");
console.log(`Tarea ejecutada en ${new Date().toISOString()}`);
console.log("###");
});
// Tarea para verificar el estado de la tarea principal (heartbeat)
const heartBeatJob = new Cron("*/5 * * * * *", () => {
console.log(
`El estado de mainJob es ${mainJob.isRunning()}, ejecución previa en ${mainJob.previousRun()}, próxima ejecución en ${mainJob.nextRun()}`,
);
});
// Detener las tareas después de 16 segundos
setTimeout(() => {
console.log("Deteniendo la demostración...");
mainJob.stop();
heartBeatJob.stop();
}, 16000);Al ejecutar este código, se obtiene un resultado similar al siguiente, demostrando un ejemplo de cron job en Node.js funcionando perfectamente:
Demostración de Croner iniciada. Esperando la próxima ejecución...
###
Tarea ejecutada en 2026-02-07T06:49:15.003Z
###
El estado de mainJob es true, ejecución previa en Sat Feb 07 2026 15:49:15 GMT+0900, próxima ejecución en Sat Feb 07 2026 15:49:20 GMT+0900
###
Tarea ejecutada en 2026-02-07T06:49:20.002Z
###
El estado de mainJob es true, ejecución previa en Sat Feb 07 2026 15:49:20 GMT+0900, próxima ejecución en Sat Feb 07 2026 15:49:25 GMT+0900
###
Tarea ejecutada en 2026-02-07T06:49:25.003Z
###
El estado de mainJob es true, ejecución previa en Sat Feb 07 2026 15:49:25 GMT+0900, próxima ejecución en Sat Feb 07 2026 15:49:30 GMT+0900
Deteniendo la demostración...Características Principales de Croner
Especificación precisa por segundos y años
Además de la sintaxis estándar de Cron (minuto, hora, día del mes, mes, día de la semana), Croner permite especificar segundos (al principio) y años (al final). Para entender mejor el formato, puedes consultar la referencia de expresiones CRON en Wikipedia.
import { Cron } from "croner";
// Ejecutar cada segundo
new Cron("* * * * * *", () => console.log("Ejecutando cada segundo"));
// Ejecutar una sola vez el 1 de enero de 2026
new Cron("0 0 0 1 1 * 2026", () => console.log("¡Feliz Año Nuevo 2026!"));También soporta notaciones extendidas como L (último día del mes), W (día laborable más cercano) y # (el n-ésimo día de la semana).
0 0 12 L * *: A las 12:00 PM del último día de cada mes.0 0 0 * * 5#3: El tercer viernes de cada mes.
Prevención de ejecuciones superpuestas
Es posible evitar que una tarea se inicie si la ejecución anterior aún no ha finalizado.
El siguiente código demuestra esta funcionalidad, creando una situación de superposición al programar una tarea para ejecutarse cada segundo, mientras que cada ejecución tarda 4 segundos en completarse.
// Función que bloquea la ejecución durante un tiempo especificado (ms)
const blockForAWhile = (ms) =>
new Promise((resolve) => setTimeout(resolve, ms));
// (Opcional) Función de callback que se invoca cuando una ejecución es bloqueada
const protectCallback = (job) =>
console.log(
`La llamada en ${new Date().toISOString()} fue bloqueada por la llamada iniciada en ${job.currentRun().toISOString()}`,
);
// Ejemplo de una tarea de larga duración
new Cron("* * * * * *", { protect: protectCallback }, async (job: any) => {
console.log(`Llamada iniciada en ${job.currentRun().toISOString()} iniciada`);
await blockForAWhile(4000);
console.log(
`Llamada iniciada en ${job.currentRun().toISOString()} finalizada en ${new Date().toISOString()}`,
);
});El resultado es el siguiente:
Llamada iniciada en 2026-02-07T07:18:34.002Z iniciada
La llamada en 2026-02-07T07:18:35.002Z fue bloqueada por la llamada iniciada en 2026-02-07T07:18:34.002Z
La llamada en 2026-02-07T07:18:36.004Z fue bloqueada por la llamada iniciada en 2026-02-07T07:18:34.002Z
La llamada en 2026-02-07T07:18:37.004Z fue bloqueada por la llamada iniciada en 2026-02-07T07:18:34.002Z
La llamada en 2026-02-07T07:18:38.003Z fue bloqueada por la llamada iniciada en 2026-02-07T07:18:34.002Z
Llamada iniciada en 2026-02-07T07:18:34.002Z finalizada en 2026-02-07T07:18:38.004Z
Llamada iniciada en 2026-02-07T07:18:39.001Z iniciada
// ... y así sucesivamenteControl de ejecución y verificación de estado
El ciclo de vida de una tarea puede depender no solo de un horario, sino también de otros disparadores.
Por ejemplo, puedes pausar una tarea cuando ocurre una excepción y reanudarla dependiendo del resultado de dicha excepción.
Se proporcionan las siguientes cuatro APIs para este propósito:
job.trigger(): Fuerza la ejecución inmediata.job.pause(): Pausa temporalmente la tarea.job.resume(): Reanuda una tarea pausada.job.stop(): Detiene permanentemente la tarea.
Adicionalmente, como se mostró en el código de ejemplo, existen APIs para verificar el estado de una tarea, como job.isRunning(), job.previousRun() y job.nextRun().
¿Por qué elegir Croner para tus tareas programadas?
Croner se presenta como una opción especialmente atractiva frente a soluciones clásicas cuando se necesita ejecución periódica simple, sin dependencias externas ni capas adicionales de manejo de fechas, y cuando se quiere reutilizar la misma lógica de programación tanto en backend como en frontend para operaciones de sondeo.
Un problema común es tener la lógica de una tarea cron gestionada en un repositorio, mientras que el archivo crontab reside en otro lugar o se omite su actualización en el servidor.
La capacidad de empaquetar tanto la programación como la lógica de la tarea en un único archivo de cron job con JavaScript o TypeScript es un beneficio significativo. Aunque esta ventaja es aplicable a otras bibliotecas, Croner lo simplifica al máximo.
Para explorar más a fondo su código fuente o su documentación completa, puedes visitar el repositorio oficial de Croner en GitHub y la página oficial de la documentación.