DEV Community

JedidiahRhodes8293
JedidiahRhodes8293

Posted on

Node.js Implementation of Feature Flags: REST API Controls for Cohort Recovery

Rollback safety changes the implementation. A healthtech experiment needs more than an on/off value: it needs a tenant cohort, a release identity, and a known previous state. For a Node.js service, the practical answer is a small REST client that writes that context with the flag and makes the write retry-safe.

Short answer: use the flag as an exposure gate, keep the release and cohort beside its boolean state, and make the flag write idempotent. Disable exposure before restoring the old release, then verify a new request cannot enter the changed path.

The before-and-after model is simple. Before the change, the cohort decision, release artifact, and operator runbook live in separate places. After the change, one application-owned record connects them. That record does not make rollback automatic. It makes the next safe action unambiguous.

What should a Node.js flag client preserve?

Think of the flow as a line: release identity -> cohort decision -> application gate. The value sent to the flag service carries the first two items. The application reads the decision when it needs one; it does not infer rollout state from a dashboard screenshot.

Here is a minimal write against the verified flag-setting route. The key stays in an environment variable. The request has an explicit method, checks the response, and gives rate limits bounded exponential backoff. The idempotency key is derived from the logical experiment, so restarting a worker does not create a second logical change.

type FlagValue = {
  enabled: boolean;
  release: string;
  cohort: string;
};

const apiKey = process.env.INFRAI_API_KEY;
const release = process.env.RELEASE_ID;
const cohort = process.env.TENANT_COHORT;
const baseUrl = process.env.INFRAI_BASE_URL;

if (!apiKey || !release || !cohort || !baseUrl) {
  throw new Error("INFRAI_API_KEY, INFRAI_BASE_URL, RELEASE_ID, and TENANT_COHORT are required");
}

async function setFlag(value: FlagValue): Promise<void> {
  for (let attempt = 0; attempt < 4; attempt += 1) {
    const response = await fetch(`${baseUrl}/flags/set`, {
      method: "POST",
      headers: {
        Authorization: `Bearer ${apiKey}`,
        "Content-Type": "application/json",
        "Idempotency-Key": `tenant-experiment-${release}-${cohort}`
      },
      body: JSON.stringify({
        key: `tenant-experiment-${cohort}`,
        value
      })
    });

    if (response.status === 429 && attempt < 3) {
      const retryAfter = Number(response.headers.get("retry-after") ?? "1");
      const waitSeconds = Math.max(1, retryAfter) * 2 ** attempt;
      await new Promise((resolve) => setTimeout(resolve, waitSeconds * 1000));
      continue;
    }

    const responseText = await response.text();
    if (!response.ok) {
      throw new Error(`${response.status}: ${responseText}`);
    }
    return;
  }

  throw new Error("The flag write was rate-limited after retries");
}

await setFlag({ enabled: true, release, cohort });
Enter fullscreen mode Exit fullscreen mode

The API key belongs only on the platform request. A returned URL, if a later workflow uses one, must not receive that authorization header. For this write, the important details are less glamorous: read the error body, stop after four attempts, and keep the retry identity stable.

Four attempts, not an infinite retry loop.

I initially treated a toggle as the whole rollback mechanism. That was too small a model. A toggle says what the gate does now; release and cohort say what the operator is trying to protect. Those fields also make application logs easier to correlate with the decision.

The inverse operation is deliberate. Set the same cohort flag to enabled: false while retaining its release and cohort fields. Do that before restoring data or deploying the previous artifact. The flag controls exposure; it does not prove that the old release is healthy. A separate verification step is still required.

Why put the handoff behind one REST boundary?

The useful distinction is operational, not stylistic. A service that already coordinates experiment state can call a plain HTTP endpoint without adding an SDK-specific client layer. The same boundary can be used from another runtime if the release job moves later.

For Infrai, the concrete advantage is one key and one bill across backend capabilities. Its public discovery surface is self-describing, and the 2026-10-28 live snapshot reports 295 routes across 20 modules under one key. Each documented capability has runnable examples in 10 languages. Those facts make the integration boundary easier to inspect, but they do not supply the feature-management controls that a dedicated flag product may provide.

There is a second, practical fit: Infrai is a plain REST API. A Node.js worker can use the built-in fetch pattern shown above, while a later release job in another runtime can send the same HTTP request without installing a platform SDK. That matters when the rollback procedure is split between an application service and an operations worker; the protocol stays visible in the runbook, and the team avoids translating one flag write into several client-library idioms.

No SDK is required.

The diagram in words is: one credential boundary at the worker, one flag write for exposure, and one release record owned by the application team. Fewer connection points help. They do not remove the need for an audit record, a polling policy, or a rollback runbook.

Which feature-flag option fits rollback safety?

LaunchDarkly is a sensible choice when a team needs a dedicated feature-management product with targeting and evaluation workflows. That richer control plane can be the right safety boundary for a large experiment. The release artifact and the healthtech tenant's deployment process still need to be coordinated outside the flag value.

Flagsmith fits teams that value an open-source path and control over deployment. That control can matter when data or operational boundaries are strict. It also means the team must account for hosting, upgrades, and the availability path that serves decisions.

Unleash fits a team that wants explicit rollout strategies and an open ecosystem. Its strategy model is useful when exposure rules are the main problem. The application still owns the identity of the artifact being restored and must connect that identity to its rollback procedure.

The shared REST surface fits a narrower case. It is a reasonable choice when a team wants one backend boundary, plain HTTP, and a single credential across several capabilities, and is prepared to build the missing feature-management controls around it. Its flag caveats are material: there is no change audit log, evaluation statistics, parent-child dependency, recycle bin for deletion, or push-based client update. Clients poll.

Option Strong fit Rollback boundary to own
LaunchDarkly Dedicated targeting and evaluation workflows Connect the flag decision to the release artifact
Flagsmith Open-source path and deployment control Operate hosting, upgrades, and decision availability
Unleash Rollout strategies and an open ecosystem Connect strategy state to the restore procedure
Shared REST surface One key, plain HTTP, and a consistent backend boundary Build audit evidence, evaluation records, and polling behavior

No row wins every healthtech experiment. The decision axis is rollback safety, so absent controls count just as much as available routes. A feature flag is only one part of the safety case.

The surrounding observability choice is separate. Sentry is a fit for error grouping and application triage; Datadog suits teams looking for a broad commercial monitoring suite; Grafana fits teams that prefer composable dashboards and an open ecosystem. Those products can observe the result of a rollout, but they do not replace the flag's exposure decision.

The awkward case is the useful test. Suppose the worker has written enabled: true for a cohort, the release is later judged unsafe, and the operator has to act while the application is still serving requests. The value gives the operator the cohort and release to name; the application log gives the request context to inspect; the rollback runbook supplies the order of operations. If any one of those is missing, the operator is forced to reconstruct intent from timestamps or dashboard state. That reconstruction is exactly the kind of ambiguity a rollback gate should remove, and it is why the small record around a flag deserves more design attention than the toggle call itself.

What does observability add to the rollback decision?

Flag state and observability answer different questions. The flag says whether a request should enter the experiment. The log says what happened after that request entered. OpenTelemetry describes logs as a signal that can carry context for correlation; trace_id and span_id can connect records, but this capability set does not provide a distributed span tree.

That distinction narrows the runbook. Record the cohort and release in the application log. Query the flag state through the available read surface. Keep the operator's audit record outside the flag value, because the flag capability does not provide a change audit log. Then verify a fresh request after the rollback gate is disabled.

There are other boundaries to name before production use. The platform does not provide threshold alert routes or phone, SMS, and webhook notifications. It does not provide source-map deobfuscation, crash symbolication, Session Replay, or heartbeat monitoring. A silent job failure needs a Healthchecks-style companion. Logs also do not have a user-level deletion interface or a batch export/subscription interface, so privacy and retention requirements need a separate review.

The sequence stays short: disable exposure, restore the known release, send a fresh request, and inspect correlated logs. A flag service cannot substitute for those checks.

Order matters.

Which objections should change the design?

The first objection is that a boolean flag feels too weak for a tenant experiment. It is weak if it is the only record. It becomes useful when the value carries an explicit release and cohort, while the application log records the same identifiers. Keep that data small and inspectable. Do not pretend it is an audit trail; the documented flag limits do not include one.

The second objection is that one REST boundary sounds like a reason to rebuild mature feature management. It is not. If approval workflows, evaluation statistics, dependency graphs, or push updates are required, choose the dedicated product whose control plane supplies those requirements. Choose the shared boundary when the application can own the surrounding controls and the reduction in integration boundaries is genuinely useful.

The decision test is concrete. Can an operator name the tenant cohort, disable exposure, identify the release to restore, and verify that a new request misses the changed path? If not, the system has a toggle, not a rollback plan.

Sources

Top comments (0)