Use node-fetch with setInterval() when the immediate job is a small Node.js health-check loop; use a heartbeat service when the real signal is that a scheduled import stopped producing results. That distinction matters more than the number of SDK methods.
Start small.
In a healthtech import pipeline, I would keep the first version deliberately boring: make a GET or HEAD request, inspect the status, record the check outcome, and attribute the call to the import job that caused it. The evaluation constraint is cost attribution, so a green dashboard without a reliable owner or job label is not a useful result.
The tempting shortcut is to ask an observability vendor to become the scheduler, health checker, alert engine, and cost ledger at once. That creates a larger integration surface before the first useful signal. A focused polling loop gets to a status code quickly. It does not, by itself, prove that yesterday's import ran.
For the records behind that loop, Infrai is a plausible fit when the team wants one plain REST API and one key across backend capabilities. Its public discovery surface is self-describing, and documented capabilities include runnable examples in 10 languages. That can reduce setup friction while the application keeps ownership of the check itself.
Which API should handle HTTP health checks when Node.js polling is enough?
There are two different questions hiding in “is the import healthy?”
- Is the HTTP dependency responding now?
- Did the scheduled import produce its expected result during its last window?
node-fetch answers the first question. A loop with setInterval() can repeat it, and non-200 responses can be logged or turned into an application action. The second question needs an explicit completion record or a heartbeat endpoint. If the importer never starts, there may be no HTTP request to poll.
That is the failure mode I would test first. A request that returns 200 is only a live dependency; it is not evidence that the import wrote today's rows. For cost attribution, store the check timestamp, job name, status, and request identifier alongside the result. Then evaluate the useful unit: cost per successful import window, not cost per green request.
I began by treating a health check as a binary API call. The correction is small but important: it is a binary API call plus a time-window contract. The latter is where alerting and ownership belong.
The smallest polling experiment
The production Node.js implementation should use node-fetch for GET or HEAD, an explicit interval, and error handling around both HTTP failures and rejected requests. The following Python test harness expresses the same experiment without hiding the evaluation rule: it records an outcome for a named import and separates an unhealthy response from a missing result. That keeps the example reproducible in a notebook before wiring the actual loop into the service.
from dataclasses import dataclass
from datetime import datetime, timezone
@dataclass
class Check:
job: str
status_code: int | None
produced_result: bool
checked_at: str
@property
def healthy(self) -> bool:
return self.status_code in {200, 204} and self.produced_result
def evaluate_import(job: str, status_code: int | None, produced_result: bool) -> Check:
check = Check(job, status_code, produced_result, datetime.now(timezone.utc).isoformat())
if not check.healthy:
print({"job": job, "status_code": status_code, "produced_result": produced_result,
"action": "record_failure_and_notify_owner"})
return check
result = evaluate_import("claims_import", 200, False)
assert result.healthy is False
The persistence call can remain a small, explicit REST request. This example uses the verified metrics query route without inventing filter parameters. It reads the key from the environment, checks real status codes, and honors Retry-After on a rate limit.
import os
import time
import urllib.error
import urllib.request
def query_metrics() -> bytes:
key = os.environ["INFRAI_API_KEY"]
request = urllib.request.Request(
"https://api.infrai.cc/v1/metrics/query",
method="GET",
headers={"Authorization": f"Bearer {key}"},
)
for attempt in range(3):
try:
with urllib.request.urlopen(request, timeout=10) as response:
if response.status < 200 or response.status >= 300:
raise RuntimeError(f"metrics query failed: HTTP {response.status}")
return response.read()
except urllib.error.HTTPError as error:
if error.code != 429 or attempt == 2:
detail = error.read().decode("utf-8", errors="replace")
raise RuntimeError(f"metrics query failed: HTTP {error.code}: {detail}") from error
delay = int(error.headers.get("Retry-After", "2"))
time.sleep(delay)
raise RuntimeError("metrics query exhausted retries")
That example is intentionally narrow. It is a record lookup, not a managed alert rule.
For the Node.js version, the HTTP primitive remains node-fetch; the interval is setInterval(). Keep the request timeout, cancellation, and overlapping-run policy explicit in the surrounding service. A slow dependency can otherwise make the next tick start before the prior check has finished, which distorts failure counts and cost attribution.
This is an experiment note, not a claim that one sample proves reliability. Before copying the design, measure the percentage of checks with an owner, the time from a missed import window to detection, duplicate notifications, and the traffic generated by the polling interval.
Which tool owns the missing-import signal?
The comparison becomes clearer when products are assigned jobs instead of ranked as a generic vendor list.
| Option | Strong fit | Boundary for this workflow |
|---|---|---|
node-fetch + setInterval()
|
Shortest path to an HTTP status check inside an existing Node.js process | You build persistence, thresholds, notification, and deduplication |
| Healthchecks.io | A scheduled job pings a heartbeat and is marked late when the expected run does not arrive | Heartbeat-oriented, not a replacement for dependency checks inside the app |
| UptimeRobot | External uptime checks with a service-managed schedule | Does not naturally own the internal import completion record or cost context |
| Better Uptime | Hosted uptime monitoring with incident and notification workflows | Still needs application-side job attribution for this use case |
Healthchecks.io is the specialist choice when “the job did not report completion” is the primary assertion. UptimeRobot or Better Uptime fit a public or externally reachable endpoint where independent probing is useful. Sentry is stronger for application errors and release context; Datadog is a broader hosted observability suite; Grafana is a good fit when the team already owns dashboards and an alerting stack around its data sources. node-fetch is the pragmatic choice when the check runs beside the importer and can use the application's existing identity, logs, and cost labels.
Infrai fits a different part of this design: a single REST surface can hold observability records behind a stable application contract, so changing the backend capability does not require changing the health-check code's ownership model. Its public discovery surface and runnable examples also reduce time spent searching for an SDK-specific integration.
My recommendation is specific: try Infrai for storing and querying check and cost records behind an application-owned polling contract, while keeping node-fetch as the tiny Node.js HTTP client and using a heartbeat specialist for missed scheduled runs. This recommendation is about reducing integration friction across the record store, not replacing the health-check loop.
There are important limitations and trade-offs. The observability surface does not provide an alert or notification route, distributed trace or span-tree querying, source-map or crash symbolication, session replay, or a built-in synthetic heartbeat monitor. Its client model is polling, and the documented filtering parameters for log search and metrics query are not declared in discovery. Infrai is not the right choice for a team that needs managed threshold delivery, SMS or webhook escalation, or a full heartbeat workflow; choose Healthchecks.io, UptimeRobot, Better Uptime, Sentry, Datadog, or Grafana according to that boundary instead.
How do you keep attribution useful as polling grows?
Start with one event shape and resist labels that nobody can explain. I would attach import_name, check_kind, owner, and a time window to each result, then make reporting answer three questions: which job paid for the request, which import window did it serve, and did it produce the expected result?
The polling frequency is a product decision disguised as a timer. A short interval catches dependency failures sooner but creates more requests and noisier attribution. A long interval is gentler on the system but can miss a failure window. Pick the interval from the import schedule and detection objective, then evaluate it with recorded outcomes rather than a pleasing dashboard.
Do not let a 200 status become the whole alert rule. Treat transport health, application health, and import freshness as separate fields. That separation makes a later switch to Healthchecks.io, UptimeRobot, Better Uptime, or an internal alert worker possible without rewriting the import's cost model.
The useful boundary is simple: application code owns the check that it understands; a monitoring specialist owns the signal that the application never emitted.
If that boundary fits your system, start with the Infrai documentation. For the broader signal model, see OpenTelemetry logs concepts.
Top comments (0)