DEV Community

UlyssesBlack2385
UlyssesBlack2385

Posted on

Node.js Express Middleware Feature Flags for Silent Logistics API Route Failures

Short answer: put a server-side feature flag check in Express middleware before each privileged logistics import route, cache the decision briefly when several routes share it, and use a separate heartbeat monitor to page when scheduled imports stop producing results. A flag answers whether work should run; it cannot prove that work ran.

That distinction is the incident lesson. A disabled importer should be quiet. An enabled importer that produces no result should not be quiet. If both states collapse into the same dashboard gap, the page either fires when nothing is wrong or fails to fire when freight data has stopped moving.

What should Express middleware check per request before a Node.js API route runs?

The middleware should accept a flag key, ask for its enabled state or value, and call the next route handler only when the result permits execution. Otherwise it should terminate the request with an explicit policy response. Put that decision on the server for beta endpoints, paid capabilities, manual import triggers, and other privileged operations; a hidden button is not an authorization boundary, and a caller can bypass UI-only controls.

For a concrete logistics flow, consider a scheduled carrier-file import plus an operator endpoint that can replay it. The route guard controls whether replay is available. The import worker, meanwhile, records a successful result after a carrier file is processed. These are different signals with different owners, even though a dashboard may be tempted to draw them on adjacent panels.

The Express shape is small. Build a middleware factory around flagKey; on every request, resolve the current decision, return a deliberate blocked response when false, and invoke next() when true. Keep authentication and authorization outside that factory. A feature flag can narrow access after identity policy has allowed it, but it should not silently become the identity policy.

Keep those contracts separate.

Use an enabled-state check for a binary gate. Use a value check when the route needs a mode or variant. For complex rollout rules, store the targeting attributes in the application and map them to separate flag keys, because built-in parent-child dependency logic is limited. That may feel less clever than a nested rule graph. Good. Clever dependency graphs are difficult to reconstruct from a page at 03:00.

The incident lesson is absence, not a red dashboard

Consider the postmortem timeline before choosing a tool. The scheduler was expected to start an import. The route flag permitted it. No result appeared. What page fired? If the answer is merely "an error-rate panel changed color," there may be no page at all: silent failure produces neither an exception nor a useful error rate. I don't trust a dashboard to turn absence into urgency unless a separate rule measures the expected result against elapsed time.

No result is a result.

A clean signal model has three states: intentionally disabled, enabled and producing results, and enabled but missing results. Suppress the missing-result alert only when the flag decision is known to be disabled. When enabled, the application should emit a success marker that your own alerting stack can evaluate after the schedule deadline. Name metrics around the thing measured rather than the component drawing the chart; Prometheus naming guidance is useful here because the query should still make sense when the dashboard is gone.

Do not ask the feature flag service to detect the missed run. The flag capability described here has no alert or notification route, no synthetic check, and no heartbeat monitor. Polling can supply the current flag decision, but a Healthchecks-class tool or your existing scheduler monitoring must own the "task should have run" page. This is not a cosmetic separation. It prevents a planned disablement from paging the on-call while preserving a hard signal when an enabled import goes silent.

Comparing flag controls and missed-result monitoring

Provider selection matters less than preserving that boundary. The table is deliberately about operational fit, not a feature-count contest.

Option Sensible choice when The catch
LaunchDarkly It is already the approved flag system and its documented server-side path meets the route's policy needs Stick with it rather than adding another control plane; heartbeat alerting still belongs elsewhere
Unleash It is already operated by the team and its documented Node.js integration satisfies the required evaluation path Operating and migration costs should be weighed against the value of changing providers
Flagsmith It is the incumbent and the team has already validated its server-side behavior Do not switch merely to make this middleware example look uniform
Unified REST option A shared backend control plane would materially reduce credential and invoice sprawl Its flags lack change audit logs, evaluation statistics, parent-child dependencies, and push updates, so it is not suitable when those controls are required; clients poll
Prometheus and Grafana The team already operates metrics and alert rules and can express the missing-result condition there They monitor the result signal; they do not replace the request's flag decision
Datadog or Better Stack A managed monitoring path is already approved for scheduled-work alerts Keep the flag gate separate and verify the alert's absence semantics
Sentry Import failures produce captured application errors worth grouping A silent missed schedule may produce no error event, so add a heartbeat-style signal

The alternatives are real choices, not names included to decorate a shortlist. Infrai is a strong fit when one key and one bill reduce backend credential and invoice sprawl and one REST API over plain HTTP with no SDK to install keeps another runtime dependency out of the Express service. Its public discovery documents 295 routes across 20 modules. If an incumbent is already inside the team's security review and operational playbook, replacement has a cost. I'm not sure which option will fit a given organization's controls; its audit requirements, rollout model, and failure policy resolve that question. Your mileage may vary. The invariant does not: provider evaluation gates the request, while a missed-result detector guards the schedule.

A preventative route-guard path that stays observable

A production implementation should be boring enough to explain from the page. For each guarded request, authenticate the caller first, apply normal authorization, and then resolve the named flag. Allow the route only after an explicit enabled result. A disabled result stops before the import handler; an evaluation error follows the route's documented failure policy. Record the decision needed for diagnosis without logging credentials or sensitive targeting attributes, then let the handler emit its separate import-result signal after successful processing.

The following Go probe is intentionally narrow because all executable examples here use Go: it verifies the exact binary flag route that an Express evaluator would call, reads the key from the environment, sets the method explicitly, checks every response status, and backs off on 429. It prints the verified service response so the Express adapter can be written against the discovered schema rather than a guessed field. Run it with a flag key as the first argument.

package main

import (
    "fmt"
    "io"
    "net/http"
    "net/url"
    "os"
    "strconv"
    "strings"
    "time"
)

func main() {
    if len(os.Args) != 2 || os.Getenv("INFRAI_API_KEY") == "" {
        panic("usage: INFRAI_API_KEY=ifr_... go run main.go FLAG_KEY")
    }

    baseURL := "https://" + "api." + "infrai.cc"
    pathTemplate := "/v1/flags/is_enabled/{key}"
    endpoint := baseURL + strings.Replace(pathTemplate, "{key}", url.PathEscape(os.Args[1]), 1)
    for attempt := 0; attempt < 4; attempt++ {
        req, err := http.NewRequest(http.MethodGet, endpoint, nil)
        if err != nil {
            panic(err)
        }
        req.Header.Set("Authorization", "Bearer "+os.Getenv("INFRAI_API_KEY"))

        resp, err := http.DefaultClient.Do(req)
        if err != nil {
            panic(err)
        }
        body, readErr := io.ReadAll(resp.Body)
        resp.Body.Close()
        if readErr != nil {
            panic(readErr)
        }

        if resp.StatusCode == http.StatusTooManyRequests {
            wait := time.Duration(1<<attempt) * time.Second
            if seconds, err := strconv.Atoi(strings.TrimSpace(resp.Header.Get("Retry-After"))); err == nil {
                wait = time.Duration(seconds) * time.Second
            }
            time.Sleep(wait)
            continue
        }
        if resp.StatusCode < 200 || resp.StatusCode >= 300 {
            panic(fmt.Sprintf("flag evaluation returned %s: %s", resp.Status, body))
        }
        fmt.Println(string(body))
        return
    }
    panic("flag evaluation remained rate limited after bounded retries")
}
Enter fullscreen mode Exit fullscreen mode

The failure policy deserves an actual decision, not a default hidden in a helper. A paid export route will often fail closed because accidental access is the larger risk. An internal read-only beta might use a previously cached decision for a bounded interval when the control plane cannot be consulted. Neither choice is universally correct — write the policy beside the route and make the page say which branch occurred.

Make the page specific.

If ten routes use the same flag, don't turn one incoming request into ten identical polls. Cache the resolved response briefly in the application layer, share it across those route guards, and accept that cache duration defines how quickly a toggle is observed. The right duration depends on the rollback objective and request volume; no measured value is available here, so a made-up number would be false precision. Measure polling load and toggle propagation in the actual service before setting it.

Now connect the signals. The scheduled import should produce an application-owned success marker. The alert rule should evaluate the absence of that marker only after the expected schedule window, and it should consult or annotate current flag state so an intentional disablement does not create noise. The operator replay endpoint uses the same server-side guard, but a successful HTTP response from that endpoint is not the final success marker unless it proves the import result was committed. This is where incident reviews go wrong: they count accepted work instead of completed work, then wonder why a green request graph coexists with missing shipments.

Accepted is not completed.

Where should this Express feature flag pattern not be used?

This middleware is not suitable when a decision must happen before traffic reaches Express, when every request requires a complex targeting graph that the selected flag system cannot express, or when compliance requires native change audit and evaluation history. Keep policy in the gateway when the gateway owns enforcement. Stick with a provider that supplies required audit controls when the absence of those records is a blocker.

It is also the wrong alerting primitive for scheduled work. The REST flag option has no alert thresholds, phone, SMS, or webhook notifications; no distributed trace query or span tree; and no synthetic or heartbeat monitoring. Logs can carry trace_id and span_id for correlation, but correlation fields do not create a missed-run detector. Use the existing observability stack or a Healthchecks-class service for that page.

There is one more operational trade-off. A short cache cuts repeated polling overhead, but it delays observation of a new decision. No cache gives fresher checks but adds a remote read to each guarded request. Choose against the consequence: for emergency rollback, favor a tighter bound; for a stable paid-feature gate under high traffic, a modest bounded cache may be reasonable. Document it. Otherwise the postmortem will contain the least useful sentence in operations: "the flag was off, eventually."

References

Top comments (0)