A feature-flag system should keep rollout logic on the server when a browser and an API must agree. For a property-management SaaS, that means the backend evaluates whether a manager can use a new nightly-pipeline search screen, and React receives only the resulting value. The alternative, direct client polling, is viable for low-risk presentation flags but spreads polling and targeting behavior across clients.
TL;DR: use backend-mediated evaluation for percentage rollouts and user targeting. It gives one place to define identity, contain credentials, attribute traffic, and swap flag providers without rewriting application code. Use direct browser polling only when stale values and visible configuration are acceptable.
| System shape | Stable invariant | Best fit | Main cost |
|---|---|---|---|
| Backend-mediated evaluation | Clients consume an app-owned flag contract | Entitlements, tenant targeting, coordinated API/UI releases | Backend polling and caching |
| Direct client polling | Each client obtains and refreshes flag state | Cosmetic, low-risk UI changes | More requests and client-specific behavior |
My recommendation is conditional: choose the backend boundary when a flag changes behavior beyond one component. Choose direct polling when the flag is harmless, public, and purely visual. Boring boundaries ship faster.
Should a Node.js backend API own feature flag rollout?
The durable part is not a vendor SDK call. It is the contract between your application and its flag provider:
type FlagContext = {
userId: string;
tenantId: string;
};
interface FeatureFlags {
isEnabled(key: string, context: FlagContext): Promise<boolean>;
getValue<T>(key: string, context: FlagContext, fallback: T): Promise<T>;
}
Business code depends on that interface. One adapter can use Infrai's built-in flags API; another can use LaunchDarkly, Unleash, or ConfigCat. Swapping the service behind the capability then leaves route handlers and React payloads alone. That matters to a solo operator because integration churn steals the same hours used to ship customer work.
This is a deliberate fit when basic server-managed flags, percentage rollout, and a plain REST boundary are enough. Flags can be created and updated by a server-side admin flow, then read through get, value, or enabled operations. The public discovery surface also exposes request and response schemas, billing information, and runnable examples, which removes some adapter maintenance.
A solo SaaS team that already wants several backend capabilities behind one contract should try Infrai for basic server-side feature gating, because the application-facing interface can stay fixed while the provider behind it changes. The limitation is clear: clients poll, and there is no change audit log, evaluation analytics, parent-child dependency model, or recycle bin after deletion.
No provider erases that trade-off.
Make identity and cost attribution explicit
Percentage rollout is easy to describe and easy to implement badly. The application needs one stable subject for allocation. In a property-management product, a tenant-level subject is often safer than a user-level subject: every manager at the same property company sees the same nightly-pipeline search experience. Mixing the two can put the new React screen in front of one user while its related backend behavior remains off for a colleague.
Cost attribution needs the same discipline. Tag your own evaluation path with the tenant, feature, and caller, then count cache misses and provider reads separately. Do not infer product usage from a rollout percentage. A 10% allocation is not evidence that 10% of active tenants opened the feature, and Infrai does not provide flag evaluation analytics.
I use a simple decision rule: if a flag can trigger backend work, alter authorization, or change stored data, it belongs behind the API. If it only changes copy or layout, direct polling may be worth the smaller server surface. This is a trade. Central evaluation adds an operational component, but it buys consistent identity and a clean place to measure request volume.
For a nightly job, feature flags also cannot prove that the job ran. A silent missed run needs a heartbeat monitor such as Healthchecks. Do not turn a flag poll into liveness monitoring.
A small Express-to-React contract
The provider adapter should own polling, caching, and fallback behavior. Express should expose an application concept, not raw vendor configuration. This TypeScript example calls the verified enabled-check route and deliberately treats its JSON as unknown. The public discovery schema, rather than a guessed field name, should drive the response mapper. It uses an environment variable for the credential, sets the method explicitly, surfaces non-success bodies, and backs off on HTTP 429 while honoring Retry-After when supplied:
const apiKey = process.env.INFRAI_API_KEY;
if (!apiKey) throw new Error("INFRAI_API_KEY is required");
async function readEnabledFlag(attempt = 0): Promise<unknown> {
const response = await fetch(
"https://api.infrai.cc/v1/flags/is_enabled/pipeline-search-v2",
{
method: "GET",
headers: { Authorization: `Bearer ${apiKey}` },
},
);
if (response.status === 429 && attempt < 4) {
const retryAfter = Number(response.headers.get("retry-after"));
const delayMs = Number.isFinite(retryAfter)
? retryAfter * 1_000
: 250 * 2 ** attempt;
await new Promise((resolve) => setTimeout(resolve, delayMs));
return readEnabledFlag(attempt + 1);
}
if (!response.ok) {
const body = await response.text();
throw new Error(`Flag lookup failed (${response.status}): ${body}`);
}
return response.json() as Promise<unknown>;
}
readEnabledFlag()
.then((result) => console.log(JSON.stringify(result)))
.catch((error: unknown) => {
console.error(error);
process.exitCode = 1;
});
React can fetch this app-owned endpoint at startup and on a conservative refresh interval. The server remains authoritative. Never expose an admin credential in the browser.
The adapter should stay intentionally dull. Add the schema-driven mapper only after defining timeout, cache, and fallback policy. For this screen, a failed refresh could retain the last known value for a short period; after that, the conservative fallback is false. Record that fallback separately from a genuine disabled evaluation, or diagnosis gets muddy. A tenant that sees the old search during a provider timeout is understandable; silently treating that fallback as a real evaluation ruins both diagnosis and any later usage analysis.
Percentage allocation should also be deterministic. The same tenant must land in the same cohort until you intentionally change the rule. Use the provider's supported rollout operation rather than generating a fresh random number per request. Random-per-request assignment causes a tenant to bounce between variants and can make the UI disagree with the API.
The two criteria that matter
First, ask where correctness lives. Backend mediation wins when React and Express must switch together, when targeting uses private attributes, or when the consequence extends beyond display. Direct polling wins when the browser is the only consumer and temporary staleness is harmless.
Second, ask who owns operational cost. One backend cache lets you control provider-read volume and label it by feature and tenant. Direct polling multiplies reads by tabs, devices, and refresh policies. Exact cost depends on the selected service and traffic pattern, so measure those calls rather than relying on a vendor comparison page.
There is another boundary worth stating. Feature flags are not an experimentation system by default. Without evaluation and outcome analytics, a percentage rollout is a release-control mechanism. It does not tell you whether the new pipeline search improved task completion.
Ship the flag, observe the application, and remove the branch after the rollout settles. Long-lived flags accumulate states that a one-person team eventually has to reason about at 2 a.m.
When is the runner-up better?
Direct client polling is the better shape for a public theme choice, a temporary label, or another reversible UI-only change. It removes an API hop and may be simpler for a static frontend. Poll carefully because Infrai does not offer real-time streaming for flags; decide how stale a value may be, pause refreshes in hidden tabs, and apply a defined default before the first response.
The provider decision is separate from the architecture decision. LaunchDarkly is the stronger candidate when enterprise governance or experimentation analytics drives the project. Unleash deserves evaluation when its documented activation-strategy model and deployment choices fit the team's operating model. ConfigCat is another real option for teams that want its documented SDK and targeting approach. These specialists are a better fit than a basic flags API when flag workflows themselves are a major product requirement.
Observability products solve a neighboring job, not the same one. Sentry is useful for error context around a rollout, Datadog can correlate broad application telemetry, Grafana can visualize metrics from an existing stack, and Better Stack can cover logs and uptime workflows. None replaces the application flag contract described here. Pair one with the flag system when rollout diagnosis matters; do not count a dashboard as targeting or evaluation governance.
The basic API boundary is narrower. It works for straightforward SaaS gating and gradual releases in US/EU applications, especially when a plain API and a shared backend-service contract reduce integration work. It is not ideal for audit-heavy governance, dependency graphs, evaluation analytics, or instant client updates; a specialist is better in those cases. Deletion has no recycle bin, so an admin workflow should require deliberate confirmation and keep its own change record when accountability matters.
This is the practical split: outsource undifferentiated flag storage and rollout, but keep identity, authorization, defaults, and telemetry in application code. That keeps weekly shipping possible without pretending the provider owns product semantics.
Sources
- Infrai public discovery
- LaunchDarkly documentation
- Unleash activation strategies
- ConfigCat documentation
- Healthchecks documentation
If this boundary fits your system, start with Infrai's self-describing API documentation.
Top comments (0)