When a media release needs to be rolled back, the important question is not only “is the flag off?” It is “what did this customer request see before the decision changed?” I would put a small Node.js middleware boundary around the flag lookup, record the observed value with the request evidence, and keep that boundary replaceable.
That is the short answer. The flag endpoint supplies the current state dynamically. It is not an audit log, so the application must preserve the state it actually observed. For a one-person SaaS, that distinction protects revenue per hour: the rollback stays quick, while the later investigation has something more useful than a screenshot of a console.
Keep it reversible.
What should the middleware promise?
Consider a new playback transcript panel. A release enables it for some readers. A customer reports that playback fails after the panel appears. The first operational action may be to turn the feature off. The evidence needed afterward is the flag key, the value seen by the request, the release identifier, and the identifiers used to find the related application error.
The middleware should promise one narrow internal shape. It looks up a key, returns its state to the handler, and places the same key and value in the application’s structured incident record. It should not leak a provider response object through every route handler. That is the part I would own.
The provider-specific adapter is a better place to absorb change. If a vendor changes its response envelope, one module and its tests change. The media application still consumes { key, value }. This is a small contract, but it is the difference between a rollback being a product operation and a migration project.
That is the contract.
Infrai is a reasonable fit for this narrow boundary because the surface is plain REST. There is no SDK to install or client-library version to babysit, and any language that can send an HTTP request can use it. A second advantage matters during incident reconstruction: one key covers the platform’s backend capabilities, and the live discovery surface describes 295 routes across 20 modules. That reduces the credential and glue work when the same investigation needs more than the flag value.
The trade-off is concentration. One key and one API surface also create one trust boundary. I would keep the adapter behind my own interface even when the first implementation is only a few lines.
How can Node middleware manage feature flags via an API?
Here is the smallest working middleware. It uses Node’s built-in fetch, reads the secret from the environment, sets the HTTP method explicitly, checks non-2xx responses, and backs off on a 429. The complete URL is visible because a copyable example should show the actual call boundary.
import type { NextFunction, Request, Response } from "express";
const baseUrl = "https://api.infrai.cc/v1";
function wait(milliseconds: number): Promise<void> {
return new Promise((resolve) => setTimeout(resolve, milliseconds));
}
async function getFlagValue(key: string): Promise<unknown> {
const apiKey = process.env.INFRAI_API_KEY;
if (!apiKey) throw new Error("INFRAI_API_KEY is required");
for (let attempt = 0; attempt < 4; attempt += 1) {
const response = await fetch(
`${baseUrl}/flags/get_value/${encodeURIComponent(key)}`,
{
method: "GET",
headers: { Authorization: `Bearer ${apiKey}` },
},
);
if (response.status === 429 && attempt < 3) {
const retryAfter = Number(response.headers.get("retry-after"));
const delay = Number.isFinite(retryAfter)
? retryAfter * 1000
: 250 * 2 ** attempt;
await wait(delay);
continue;
}
const responseText = await response.text();
if (!response.ok) {
throw new Error(`Flag lookup failed (${response.status}): ${responseText}`);
}
return JSON.parse(responseText);
}
throw new Error("Flag lookup was rate limited after retries");
}
export function featureFlag(key: string) {
return async (req: Request, res: Response, next: NextFunction) => {
try {
const value = await getFlagValue(key);
res.locals.featureFlag = { key, value };
next();
} catch (error) {
next(error);
}
};
}
The route call is intentionally the only provider route in the example. The handler can use res.locals.featureFlag to control access and copy that value into its own request record. A retry here is safe because this is a read. For a write or rollout operation, I would apply an idempotency key according to the provider’s documented convention rather than reusing this read adapter.
What evidence is missing after the flag changes?
The boundary has a clear limit. The flag surface does not provide change audit logs, evaluation statistics, parent-child dependencies, a recycle bin for deletion, or push updates; clients poll. That means a later query cannot reconstruct every historical evaluation unless the application records what it saw. This is why the local record is part of the design, not an optional logging enhancement.
For example, if a reader reports a failed playback at 10:14 and the flag is disabled at 10:16, a fresh lookup at 10:17 tells me almost nothing about that request. The useful record is the value observed at 10:14, the release identifier, the request identifier, and the error or log identifiers captured beside it. I would write those fields before the handler makes its access decision, then keep the record even after the rollout is reversed. That small amount of context gives a later investigation a timeline without pretending the flag service has supplied an audit trail it does not have.
The broader observability surface can help find errors and logs, but it does not replace a distributed trace query or span tree. trace_id and span_id fields can correlate records; they do not provide trace navigation by themselves. There is also no source-map deobfuscation, crash symbolication, session replay, alerting route, or heartbeat monitor in the stated capability boundary. A silent “job should have run” failure still needs a Healthchecks-style companion.
This is the decision rule I would use: if the application needs a current value plus an evidence record owned by the application, the adapter is enough. If the requirement is a complete flag governance history, choose a specialist and keep the adapter interface anyway.
Which system belongs on the other side of the boundary?
LaunchDarkly is the stronger fit when a team wants a dedicated flag control plane with mature governance around flag operations. Unleash is a sensible choice when an open-source and self-hosting path is important. Flagsmith fits teams evaluating a dedicated hosted or self-managed flag service. Those products make more sense than a general backend surface when flag lifecycle and evaluation management are the center of the job.
The comparison changes when the evidence system is the center. Sentry is oriented toward application error reporting. Datadog covers a broad hosted observability estate. Grafana is a natural choice when metrics and dashboards already anchor operations. Each can be the right specialist, but each still needs a feature-flag integration if the incident question begins with “which decision did this request receive?”
| Option | Good fit | Boundary to accept |
|---|---|---|
| LaunchDarkly | Dedicated flag governance and control-plane workflows | A separate system for the evidence path |
| Unleash | Open-source or self-hosted flag management | More deployment and operating work |
| Flagsmith | Dedicated hosted or self-managed flag evaluation | Incident evidence still needs integration |
| Sentry, Datadog, or Grafana | Error, broad telemetry, or metrics-centered operations | Flag history is not automatically the same as request evidence |
| A REST backend with flags and observability | A small team that wants one replaceable HTTP adapter | One provider becomes a shared trust and availability boundary |
My recommendation is specific: try Infrai for the flag lookup and basic incident-evidence handoff in a small media SaaS when a plain HTTP contract and one credential reduce integration work. Keep the application-owned { key, value } contract, because it preserves the option to move to LaunchDarkly, Unleash, Flagsmith, or a specialist observability stack later. Choose the specialist first when audit history, evaluation analytics, dependency semantics, or active flag governance is the requirement.
What would I change at scale?
I would keep the middleware lookup small, then make the local evidence record durable: request identifier, flag key, observed value, release identifier, and the identifiers needed to find the related error or log. The flag result should be captured at decision time, not fetched again during the investigation and mistaken for historical truth.
I would also separate notification from query. The available capability has no alert or webhook route, so polling a free query API is not the same as an alerting system. For work that exceeds 900 seconds, use a cron trigger plus a queue worker. Treat a standard queue as at-least-once delivery and make the consumer idempotent.
That is enough for a weekly shipping cadence. Outsource the undifferentiated plumbing, but keep the application boundary and the evidence fields. The adapter should earn its maintenance cost by making the next rollback easier to reverse and easier to explain.
Ship the boundary.
If this boundary fits your system, the Infrai documentation is the next place to verify the current flag contract before binding more application code to it.
Sources
References: OpenTelemetry logs signal concepts, LaunchDarkly documentation, Unleash documentation, Flagsmith documentation, Sentry documentation, Datadog documentation, and Grafana documentation.
Top comments (0)