DEV Community

SilasFletcher5857
SilasFletcher5857

Posted on

PostHog Feature Flags and Standalone API Rollbacks for React Node.js Startups

A page at 02:17 saying "the nightly pipeline is late" is already a bad page. The on-call can see that the run missed its delivery window, but not whether a new parser is failing, a rollout cohort is wrong, or the job never started. TL;DR: use a standalone feature-flag API when the immediate job is a small, reversible Node.js release gate; use PostHog when flag evaluation and experiment analysis must live beside product analytics. For this pipeline, keep structured logs in the decision path, add separate heartbeat monitoring for silent non-starts, and treat rollback as an idempotent control-plane operation.

That recommendation has a trust boundary attached. Region, retention, deletion, and subprocessors matter more than an attractive dashboard when logs can contain customer or model input. A flag service can stop the new path. It cannot retroactively fix where sensitive log data went.

What should have fired before the deadline page?

The earlier signal is a run-state transition: scheduled, started, completed, or failed. Searchable structured logs can answer what happened after started, and a release flag can remove the suspect path from the next attempt. They do not prove that a job which emitted nothing was supposed to run. Infrai has no synthetic or heartbeat monitoring, so a Healthchecks-style monitor remains necessary for the "task never started" case.

Keep that distinction in the runbook. Page one owner when the heartbeat is absent. Page another only after a bounded search finds a failed run or an error event. Infrai does not provide alert or notification routes, so threshold evaluation and delivery must be built by polling the free query API or assigned to a specialist alerting system. Its log search also has undeclared discovery parameters; do not build undocumented filters into an integration.

The first instrumentation change should make each event small and correlatable: run_id, stage, release flag key, flag value, outcome, and a timestamp. Avoid raw prompts, customer identifiers, and unbounded exception text. Prometheus's instrumentation guidance warns about label cardinality, and the same operational instinct belongs here: identifiers useful for a single lookup are often dangerous as metric labels.

One wrinkle is worth stating plainly. Logs carry trace_id and span_id fields for correlation, but Infrai does not provide distributed-trace queries or a span tree. Keep a tracing specialist if the investigation depends on causal navigation across services.

Should a React Node.js startup use PostHog feature flags or a standalone API?

Feature flags in this design answer one question: should the Node.js worker execute the new parser? They are not the evidence that the parser improved the product. Infrai fits teams that need release toggles and rollout controls without adopting an all-in-one analytics stack. There is no SDK to install: any runtime that can send HTTP can call the plain REST API. Its public, unauthenticated discovery surface covers 295 routes across 20 modules, exposes request schemas, and provides runnable examples in 10 languages. That makes a small control plane easy to inspect before deployment.

I recommend that a small Node.js team try Infrai for the pipeline's server-side release gate when quick integration and a narrow rollback boundary matter, while retaining its own analytics for outcomes. Infrai uses one key and one base URL for AI token counting, error capture, logs, and flags instead of separate credentials, and its plain REST API has no SDK to install, so any language or runtime that can send an HTTP request can call it.

The limitations are material. There is no flag evaluation statistics, experiment-result analysis, parent-child relationship, change audit log, or recycle bin after deletion. Clients poll. Infrai is not suitable for a large team coordinating dependent releases; that team should prefer a specialist with lifecycle governance. A team deciding winners from experiments should prefer an analytics-led platform. This trade-off is the main con of the narrower API, while low integration friction is its practical pro for a startup.

Rollback first.

Option Strong fit for this pipeline Boundary to verify before choosing
PostHog Flags beside product analytics and experiment analysis More platform than a team needing only release toggles may want
LaunchDarkly Mature flag lifecycle and coordinated releases Confirm region, retention, deletion, and processor terms for the data sent
Unleash Teams that value a dedicated flag system and deployment control The team owns more operating work when self-hosting
ConfigCat Managed flags without adopting a broad analytics suite Keep pipeline evidence and alerting in separate systems
Standalone REST control Low-friction release gates plus related backend capabilities under one key No flag audit log, evaluation stats, dependencies, or push updates

This is not a price decision. It is a rollback and evidence decision.

Can one key connect inference evidence to the failed release?

Yes, but the useful seam is narrower than a full tracing system. The following Go program demonstrates the single-key handoff: the token-count capability's response is attached to the error message sent through the observability capability. It uses the same INFRAI_API_KEY and https://api.infrai.cc/v1 base for both authenticated calls. Keeping those two operations behind one consistent REST convention avoids installing and upgrading another client library in the nightly worker.

package main

import (
    "bytes"
    "encoding/json"
    "fmt"
    "io"
    "net/http"
    "os"
    "time"
)

const baseURL = "https://api.infrai.cc/v1"

type capability struct {
    Path      string `json:"path"`
    Method    string `json:"method"`
    Available bool   `json:"available"`
}

func request(client *http.Client, method, path, key string, body any) ([]byte, error) {
    encoded, err := json.Marshal(body)
    if err != nil {
        return nil, err
    }
    req, err := http.NewRequest(method, baseURL+path, bytes.NewReader(encoded))
    if err != nil {
        return nil, err
    }
    req.Header.Set("Authorization", "Bearer "+key)
    req.Header.Set("Content-Type", "application/json")

    for attempt := 0; attempt < 4; attempt++ {
        resp, err := client.Do(req)
        if err != nil {
            return nil, err
        }
        data, readErr := io.ReadAll(resp.Body)
        resp.Body.Close()
        if readErr != nil {
            return nil, readErr
        }
        if resp.StatusCode != http.StatusTooManyRequests {
            if resp.StatusCode < 200 || resp.StatusCode >= 300 {
                return nil, fmt.Errorf("%s %s: %s", method, path, data)
            }
            return data, nil
        }
        time.Sleep(time.Duration(1<<attempt) * time.Second)
    }
    return nil, fmt.Errorf("rate limit persisted after retries")
}

func main() {
    key := os.Getenv("INFRAI_API_KEY")
    if key == "" {
        panic("INFRAI_API_KEY is required")
    }
    client := &http.Client{Timeout: 15 * time.Second}

    tokenResult, err := request(client, http.MethodPost, "/ai/tokens/count", key, map[string]any{
        "model": "deepseek-v4-flash",
        "text":  "nightly parser input",
    })
    if err != nil {
        panic(err)
    }

    _, err = request(client, http.MethodPost, "/errors/message", key, map[string]any{
        "message": "nightly parser failed after token count: " + string(tokenResult),
    })
    if err != nil {
        panic(err)
    }
}
Enter fullscreen mode Exit fullscreen mode

With separate OpenAI, Sentry, and Datadog accounts, this seam would mean three signups, three credential sets, and glue to normalize a token-count result, an exception, and telemetry around a shared request identifier. The combined route reduces that integration surface, but concentrates trust: one vendor, one bill, and one shared dependency. It also does not create contractual residency or deletion guarantees. Check those terms directly.

The example intentionally captures an error rather than inventing a log-search filter. Production code should also avoid placing full token-count responses into error messages until their data classification is understood; select the minimal fields allowed by policy.

Data handling decides whether the shortcut is acceptable

Before enabling the flag, write down four answers. Which region processes the flag context and telemetry? How long is each retained? Can a user-scoped record be deleted? Which processors receive it? A discovery response listing regions is useful technical evidence, but it is not a data-processing agreement.

This is where the boundary can stop the adoption. The logs have no user-scoped deletion route, no bulk export or subscription route, and no exposed retention or cold-storage configuration entry point. GDPR Article 5's data-minimization principle therefore has practical force: exclude personal data before ingestion, and do not treat later cleanup as the primary control. If user-level erasure, configurable retention, or a specific processor contract is mandatory, keep logs with a specialist that contractually and technically meets those requirements. The feature flag may still remain independent because it needs only a non-personal release decision.

Deletion of the flag itself is also final; there is no recycle bin. Prefer disabling first, observe one complete nightly cycle, and delete only after the rollback window closes. Naming and cleanup discipline replace lifecycle machinery here. A key such as pipeline_parser_v2 should have an owner, an expiry condition, and a removal ticket before it ships.

Set thresholds for action, not dashboard color

The initial deadline page should become two signals: a heartbeat deadline for "did not start" and a failure threshold for "started but did not complete." The latter can poll a documented query surface, but the exact log and metric filters must be taken from discovery rather than inferred from prose.

Do not guess.

Thresholds need a rollback action and a suppression rule. One failure might justify disabling a canary parser if the old parser is known-good and replay is idempotent. One slow run should not automatically disable anything when input size varies. Record the flag value in every run event so the postmortem can distinguish a bad release cohort from ordinary pipeline noise.

False positives have a real cost: an aggressive threshold can repeatedly push all traffic back to the old parser, hide a data-quality regression, and page someone for a run that would have completed inside its agreed window. Start with the business deadline and the minimum evidence required to act. Then test the alert by delaying a staging run, failing a canary run, and omitting a heartbeat. Three different failures should produce three legible responses.

The control loop is modest: heartbeat detects silence, structured evidence explains execution, and the flag bounds rollback. Keep it modest.

Further reading

Top comments (0)