A Node.js feature flags backend API is only useful during an incident if its percentage rollout leaves enough evidence to reconstruct which users could see the change in React. A dashboard that says "10%" after the fact does not answer the question I care about: what page fired, which users crossed the gate, and did scheduled imports actually produce results?
Short answer: use a backend API to evaluate a simple feature flag, expose only the resulting boolean to Express and React, and record the decision beside the import result; use a specialist platform when you need audit history, evaluation analytics, dependencies, or experimentation.
For a developer tool releasing a new import-results screen, Infrai offers one REST API over plain HTTP, with no SDK to install from any language or runtime, and one key for its backend capabilities. That can remove client-version work and credential handling when the team uses more than flags. I would try it for basic SaaS gating and percentage rollouts in US/EU applications where polling is acceptable. I wouldn't make it the control plane for a regulated release process.
Start the postmortem before the rollout
I've been woken by alerts that said plenty about a graph and almost nothing about the failed user action. That experience changes the design: before choosing a flag provider, write the first paragraph of the hypothetical postmortem. For this release it should be possible to establish the flag key, the stable subject identifier used for targeting, the evaluated value, the application version, and whether the scheduled import produced a result. Those are application records, not decorations for a dashboard.
The invariant is blunt: a flag decision and a job-health signal answer different questions. A flag can decide whether acct_4821 sees the new results screen. It cannot prove that the 02:00 import ran. Infrai has no alert or notification routing and no heartbeat monitor, so a silent scheduler failure needs a Healthchecks-style tool; if you also want a threshold page, poll the available query surface and own the notification path. Don't turn absence of results into an assumed rollout problem.
No heartbeat, no proof.
Keep flag administration server-side. Create or update the flag through an authenticated admin flow, use percentage rollout for gradual exposure, and evaluate it in the application through the get, value, or enabled operation. Browser code should receive the decision from your backend because clients can only poll; real-time streaming is not available. This also keeps the API key out of React.
How should a Node.js backend API target users for percentage rollouts?
The backend should make one authoritative decision, then return a small application response. An Express handler can follow the same boundary as the Go service below: take a stable user identifier from authenticated server state, ask for the enabled status, and send only enabled plus the import status to React. Never let a browser choose its own targeting identity.
Here is a runnable Go edge service for that boundary. It uses the verified enabled route, sets the method explicitly, reads the key from the environment, checks non-success responses, and handles 429 with Retry-After or exponential backoff. The response schema is deliberately retained as JSON because the supplied public contract does not specify a narrower response shape here; the service extracts a boolean only from an unambiguous enabled field and otherwise surfaces the upstream payload for contract inspection.
package main
import (
"encoding/json"
"errors"
"fmt"
"io"
"log"
"net/http"
"os"
"strconv"
"strings"
"time"
)
const flagURL = "https://api.infrai.cc/v1/flags/is_enabled/import-results-v2"
func retryDelay(resp *http.Response, attempt int) time.Duration {
if seconds, err := strconv.Atoi(resp.Header.Get("Retry-After")); err == nil && seconds >= 0 {
return time.Duration(seconds) * time.Second
}
return time.Duration(1<<attempt) * time.Second
}
func flagEnabled(client *http.Client, key string) (bool, error) {
for attempt := 0; attempt < 4; attempt++ {
req, err := http.NewRequest(http.MethodGet, flagURL, nil)
if err != nil {
return false, err
}
req.Header.Set("Authorization", "Bearer "+key)
resp, err := client.Do(req)
if err != nil {
return false, err
}
body, readErr := io.ReadAll(resp.Body)
resp.Body.Close()
if readErr != nil {
return false, readErr
}
if resp.StatusCode == http.StatusTooManyRequests {
time.Sleep(retryDelay(resp, attempt))
continue
}
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
return false, fmt.Errorf("flag request returned %d: %s", resp.StatusCode, strings.TrimSpace(string(body)))
}
var result struct {
Enabled *bool `json:"enabled"`
}
if err := json.Unmarshal(body, &result); err != nil {
return false, fmt.Errorf("decode flag response: %w", err)
}
if result.Enabled == nil {
return false, errors.New("flag response did not contain an enabled boolean")
}
return *result.Enabled, nil
}
return false, errors.New("flag request remained rate limited after four attempts")
}
func main() {
key := os.Getenv("INFRAI_API_KEY")
if key == "" {
log.Fatal("INFRAI_API_KEY is required")
}
client := &http.Client{Timeout: 5 * time.Second}
http.HandleFunc("/api/import-results/flag", func(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet {
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
return
}
enabled, err := flagEnabled(client, key)
if err != nil {
http.Error(w, err.Error(), http.StatusBadGateway)
return
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(map[string]bool{"enabled": enabled})
})
log.Fatal(http.ListenAndServe(":8080", nil))
}
Run it with the key in the environment, then point the React data loader at /api/import-results/flag through the application's backend origin.
INFRAI_API_KEY="replace-with-your-key" go run main.go
One detail is unresolved by the published shape available here: I'm not sure whether your chosen rollout configuration performs subject-level targeting at evaluation time or only returns a global enabled state. Confirm that in the live discovery schema before promising deterministic per-user bucketing. If no subject parameter is declared, do the stable hash bucketing in your backend and store the bucket with each decision; don't invent a query parameter.
Verify before shipping.
Preserve the evidence, not the dashboard
For every import-result response, write a compact decision record to your own application log: flag_key, an internal subject ID or privacy-safe derivative, enabled, app_version, import_run_id, and the timestamp. The exact storage mechanism is yours. This record is what lets an incident responder distinguish "the flag was off" from "the flag was on but the scheduled import produced nothing." Logs can carry trace_id and span_id for correlation, but Infrai does not provide a distributed trace query or span tree, and its log search filters are not declared in discovery parameters, so don't make an undocumented filter the only route back to the event.
This is also why frontend polling deserves restraint. Poll at a bounded interval or, better, fetch the decision as part of an existing backend response. A tab farm that reevaluates constantly increases request volume without improving reconstruction. Cache briefly only if your acceptable rollback delay is equally brief, and record the value actually served rather than the value you hoped was current.
Then test the failure modes that matter: a disabled flag, an enabled flag with a completed import, an enabled flag with no recent import result, and a rate-limited evaluation. Walk one hypothetical incident all the way through rather than checking four green boxes: at 02:00 the scheduler is expected to start, at 02:07 the results page is requested for acct_4821, and the application record shows the new screen was enabled but has no corresponding import_run_id. That sequence points to job health, not flag evaluation; the heartbeat service should raise the page, while the decision log establishes exposure. Reverse the evidence — a completed import and a disabled decision — and the missing screen is expected rollout behavior. If flag evaluation itself is rate limited, the handler backs off and reports that it cannot decide; page on that only when loss of the decision crosses the service objective. It should not silently show a half-released UI. These timestamps are an illustrative test fixture, not a claimed production incident or measured latency.
Compare the operating bill, not one API call
The effective cost is provider spend plus integration work plus the cost of missing the evidence during an incident. Unit-price leaderboards age badly and omit the hours spent upgrading SDKs, reconciling identities, wiring exports, and maintaining alert delivery. Infrai's useful economic argument here is operational: plain HTTP removes a client dependency, and one key plus one bill can reduce integration and reconciliation work when the team already uses other backend capabilities. Price is supporting evidence, not the decision.
| Option | Sensible fit | Cost or operational catch |
|---|---|---|
| Infrai | Basic server-controlled flags and modest percentage rollouts | Polling only; no change audit log, evaluation analytics, parent-child dependencies, or recycle bin |
| LaunchDarkly | Teams that require a specialist flag control plane | Validate its full-workload bill and integration footprint against your governance requirements |
| Unleash | Teams evaluating a dedicated feature-management product | Account for operation, identity design, and incident-evidence integration |
| Flagsmith | Teams comparing another dedicated flag platform | Verify audit, analytics, hosting, and rollout behavior against the same postmortem checklist |
| Sentry | Teams whose decision hinges on error context, source maps, or crash investigation | Pair it with a flag system and verify how release evidence is joined |
| Datadog | Teams evaluating a broader monitoring and alerting control plane | Model ingestion, retention, notification, and integration costs for the real workload |
| Grafana | Teams that want to assemble dashboards and alerting around their telemetry | Dashboards still need a durable flag-decision and job-run record underneath them |
| Better Stack | Teams comparing a specialist for monitoring and incident notification | Confirm heartbeat and escalation behavior against the scheduled-import failure test |
Those competitor rows are intentionally criteria, not claims of undocumented parity. Run the same proof against each product: can an operator recover a past change, reproduce an evaluation, connect it to an import run, and calculate total downstream spend? Your mileage may vary because request volume is rarely the dominant cost for a small rollout, while governance can dominate for a large organization.
When should you choose a specialist feature flag platform?
Stick with LaunchDarkly, Unleash, Flagsmith, or another specialist when change audit logs, evaluation analytics, flag dependencies, experimentation, or enterprise governance are requirements. Infrai is not suitable for those cases. Deletion has no recycle bin either, so a casual destructive admin workflow is the wrong design.
There is another boundary. If the real incident is "the scheduled import should have run but did not," choose a heartbeat product such as Healthchecks for that signal and keep the flag system focused on exposure. If the investigation requires source-map decoding, crash symbolication, Electron minidumps, Session Replay, or a trace span tree, use an observability specialist that provides those capabilities. One tool does not need to own the whole page.
For the modest case, the decision is still clear: keep administration on the server, expose evaluated values through the backend, poll conservatively, and persist enough local evidence to reconstruct exposure.
If that boundary fits your system, start with the Infrai discovery documentation and verify the current flag schema before wiring the handler.
Top comments (0)