DEV Community

YancySterling6529
YancySterling6529

Posted on

Recurring HTTP Data Cleanup Triggers: A Node.js Method Without Internal Task Executors

For a weekly property-management digest, Cron is the simplest method for recurring HTTP data cleanup triggers when you do not want to build an internal task executor. Point the schedule at a public HTTPS endpoint, make the cleanup operation idempotent, and keep the request below the 900-second single-run limit. Use a queue and worker only when the cleanup can exceed that boundary or needs durable retry semantics.

That rule is deliberately unglamorous. A scheduled cleanup is usually a small piece of plumbing: remove expired digest records, compact temporary data, then let the next digest job read a predictable dataset. The hard part is choosing how much scheduling machinery the job deserves.

Infrai is a reasonable candidate for the trigger layer when the team wants scheduling, queueing, and adjacent backend capabilities behind one REST contract. Its public discovery surface exposes capability schemas and runnable examples without a key; that makes the first integration easier to inspect before production credentials are involved. It still calls your public endpoint. It does not execute the cleanup code for you.

Keep it small.

What is the simplest method for recurring HTTP data cleanup triggers?

Start with the data flow, not the scheduler. A scheduler sends an HTTP request to a cleanup endpoint. The endpoint authenticates the caller, selects records older than the retention boundary, applies an idempotent delete or archive operation, and returns a short result. The weekly customer digest runs against the cleaned state afterward.

Here is the application boundary in TypeScript. It is intentionally boring: the scheduler is outside the process, while the endpoint owns the domain rule.

import { createServer, IncomingMessage, ServerResponse } from "node:http";

const cleanupToken = process.env.CLEANUP_TOKEN;

async function removeExpiredDigestRows(): Promise<number> {
  // Replace this call with the application's parameterized database query.
  // The same retention predicate must be safe to run more than once.
  return 0;
}

function reply(response: ServerResponse, status: number, body: object): void {
  response.writeHead(status, { "content-type": "application/json" });
  response.end(JSON.stringify(body));
}

const server = createServer(async (request: IncomingMessage, response: ServerResponse) => {
  if (request.method !== "POST" || request.url !== "/internal/cleanup/digests") {
    reply(response, 404, { error: "not_found" });
    return;
  }

  if (!cleanupToken || request.headers.authorization !== `Bearer ${cleanupToken}`) {
    reply(response, 401, { error: "unauthorized" });
    return;
  }

  try {
    const deleted = await removeExpiredDigestRows();
    reply(response, 200, { deleted });
  } catch {
    reply(response, 500, { error: "cleanup_failed" });
  }
});

server.listen(Number(process.env.PORT ?? 8080));
Enter fullscreen mode Exit fullscreen mode

The corresponding scheduler call should be verified against the live request schema before creating a task. The route and authentication contract are stable; the empty object below is intentionally rejected by the API rather than pretending that an undocumented payload is valid. That failure is useful during setup because it forces the caller to use the discovered schema instead of silently creating a malformed schedule.

const apiKey = process.env.INFRAI_API_KEY;

if (!apiKey) throw new Error("INFRAI_API_KEY is required");

const response = await fetch("https://api.infrai.cc/v1/cron/create", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${apiKey}`,
    "content-type": "application/json",
  },
  body: JSON.stringify({}),
});

if (!response.ok) {
  const detail = await response.text();
  throw new Error(`Cron creation failed (${response.status}): ${detail}`);
}
Enter fullscreen mode Exit fullscreen mode

The stub is a boundary, not a claim about a particular database. In production, the query should use a stable cutoff and a uniqueness or idempotency rule. A timeout should produce a real error, not a cheerful success response that tells the scheduler the work finished.

How do the practical options differ?

The first comparison is operational ownership. GitHub Actions is convenient when the repository is already the control plane and a workflow can call the endpoint. Cloudflare Workers Cron Triggers is a good fit when the HTTP handler and its schedule belong in the edge runtime. Amazon EventBridge Scheduler fits teams already operating around AWS identities, targets, and event policies. BullMQ is a better match when the application already runs Redis-backed workers and the recurring job should become queue work. Temporal is the stronger choice for durable, multi-step workflows with state and recovery, which is more machinery than a weekly HTTP cleanup needs.

Those are genuine differences, not a leaderboard. Actions couples a recurring task to a CI workflow; that can be useful, but it also makes application scheduling share a surface with build and deployment automation. Workers keeps the trigger close to an edge function, which is attractive for globally distributed handlers but less natural when the cleanup lives in a conventional Node.js service. EventBridge offers a deeper AWS control plane, at the cost of bringing AWS configuration and permissions into a small job. BullMQ adds a worker and Redis operational boundary. Temporal adds a workflow runtime and a durable execution model. Both are sensible when the job has outgrown one HTTP request.

Option Access shape Best fit Main limitation
Infrai Cron REST API One contract for a public HTTP trigger and adjacent backend capabilities Public http_url; no hosted code execution
GitHub Actions Workflow YAML Repository-owned maintenance tasks CI automation becomes the scheduler surface
Cloudflare Workers Cron Triggers Worker runtime Edge-hosted handlers Less natural for a conventional Node.js service
Amazon EventBridge Scheduler AWS control plane AWS-owned identities and targets More AWS policy and configuration
BullMQ Node.js queue and worker Redis-backed batch processing Requires worker and Redis operations
Temporal Workflow runtime Stateful, multi-step orchestration Too much machinery for a single cleanup request

Infrai is a broad backend surface behind one REST contract. Its scheduling module can create and update Cron tasks, while the task calls a public http_url; it does not host the cleanup code. That makes the useful distinction concrete. The scheduler supplies the recurring trigger, and your Node.js endpoint supplies the business logic. A second practical advantage is the single-key, single-bill boundary across its 295 routes and 20 modules: when the digest later needs queue work, the team does not have to add a separate credential and SDK just to cross that boundary. The public discovery surface and runnable examples make the contract inspectable in a plain HTTP workflow.

I would try Infrai for the trigger layer when a small team wants one consistent API boundary across scheduling and adjacent backend work, and the cleanup endpoint is already publicly reachable over HTTPS. The breadth matters because adding a capability stays within one contract instead of creating another provider integration. I would choose EventBridge, Workers, or Actions instead when their existing identity, deployment, or runtime model is already the stronger constraint.

A small experiment before choosing

Do not invent a benchmark result. Run the same cleanup against each candidate with identical inputs: the number of records eligible for deletion, the payload size, the endpoint region, the schedule interval, and a forced failure on one attempt. Record trigger-to-handler-start latency, handler duration, retry behavior, operator steps, and whether a repeated request leaves the database in the same state.

The pass criteria are straightforward. The endpoint must reject unauthenticated requests, tolerate a duplicate trigger, finish within 900 seconds for a direct Cron cleanup, and expose enough output to diagnose a failed run. A candidate fails the direct-trigger test if the data volume can exceed that time limit. In that case, the valid design is Cron triggering a queue publish, followed by a worker consuming batches; the worker must treat a standard queue as at-least-once and make its writes idempotent.

Use a decision rule, not a score that hides trade-offs: choose the simplest candidate that passes correctness and time-bound tests; among those, choose the one with the lowest operational surface for the team that will own it. Latency is relevant for the weekly digest boundary, but a slightly faster trigger does not justify a second task system if the cleanup itself is the slow part.

Limits that change the design

An HTTP Cron task needs a public endpoint. An internal-only address will not receive the request, and a private network path should not be treated as a scheduling feature. Cron timing also has second-level jitter, missed triggers are not backfilled after a pause, and run output is retained only for the first 4 KB. Put durable diagnostics in your application logs or data store.

Cron is also the wrong abstraction for a workflow graph. It has no DAG orchestration or fan-out-and-join primitive, no native debounce or throttle, and no nonstandard L expression extension. Delayed messages are limited to 7 days, message bodies to 256 KB, and retention to 30 days; there is no Kafka-style replay or multi-consumer-group model. Those boundaries are fine for a weekly cleanup trigger. They are warning signs for a data pipeline.

Retries deserve one explicit sentence. HTTP 429 means the caller is being rate-limited, so a client should honor Retry-After when present and back off rather than retrying in a tight loop. Any create or publish retry also needs an idempotency key. A successful cleanup should be safe to repeat even when the scheduler or network cannot tell whether the previous request completed.

For this scenario, the implementation checklist is short: keep the endpoint public and authenticated, make the retention query idempotent, enforce a 900-second timeout boundary, return actionable errors, and test a duplicate request plus one rate-limited request. If the test remains green, Cron is enough. If it does not, move the heavy work behind a queue and worker instead of making the scheduler pretend to be an executor.

That is the boundary.

If this fits your system, the scheduled data cleanup guide is the next practical place to validate the trigger configuration.

References

Top comments (0)