| Variable | Default | Purpose |
|---|---|---|
GNEWS_API_KEY |
— | Required at runtime (except NODE_ENV=test). |
PORT |
3000 |
HTTP listen port. |
NODE_ENV |
— | Use production in deployed environments. |
LOG_LEVEL |
info (non-test) |
Pino level (trace–fatal, or silent). |
GNEWS_BASE_URL |
https://gnews.io/api/v4 |
Upstream provider base URL. Override only for local integration tests, benchmarks, or compatible provider mocks. |
HTTP_TIMEOUT_MS |
15000 |
Outbound GNews request timeout (max 60000). |
STALE_CACHE_TTL_SEC |
3600 |
Longer-lived stale article cache TTL used only as an upstream-failure fallback (min effective value >600, max 86400). |
UPSTREAM_CIRCUIT_FAILURE_THRESHOLD |
3 |
Consecutive provider failures before the circuit opens. |
UPSTREAM_CIRCUIT_COOLDOWN_MS |
30000 |
How long to short-circuit provider calls after the circuit opens (max 300000). |
SHUTDOWN_TIMEOUT_MS |
10000 |
Force-exit if server.close does not finish. |
RATE_LIMIT_MAX |
120 |
Max requests per IP per window. |
RATE_LIMIT_WINDOW_MS |
60000 |
Rate-limit window. |
DISABLE_RATE_LIMIT |
0 |
Set to 1 to disable limiting (emergency only). |
TRUST_PROXY |
0 |
Set to 1 behind a reverse proxy so rate limits use X-Forwarded-For. |
REDIS_URL |
— | If set (e.g. redis://localhost:6379), article search results are cached in Redis with the same TTL as in-memory mode. Omit to use the in-process cache only. |
CLIENT_API_KEYS |
— | Comma-separated secrets. When set, every /api/* request must send header X-API-Key matching one value. Omit to allow unauthenticated API access (still use network controls in production). |
OTEL_EXPORTER_OTLP_ENDPOINT |
— | Base OTLP URL (e.g. http://jaeger:4318). Traces POST to /v1/traces. Enables tracing when set. |
OTEL_EXPORTER_OTLP_TRACES_ENDPOINT |
— | Full traces URL; overrides the base + /v1/traces combination. |
OTEL_SERVICE_NAME |
news-api |
service.name resource attribute. |
OTEL_TRACING_ENABLED |
0 |
Set to 1 to export traces to default http://127.0.0.1:4318/v1/traces when no OTLP endpoint env is set (local dev). |
- Liveness:
GET /health— process is up. - Readiness:
GET /ready—200whenGNEWS_API_KEYis set (non-test);503if not.
GET /openapi.yaml— served fromdocs/openapi.yamlrelative to the process working directory. The production image setsWORKDIR /appand includes that file underdocs/openapi.yaml.
- No
REDIS_URL: in-process cache (node-cache, 600s TTL). Each replica has its own entries. REDIS_URLset: responses are cached in Redis with the same TTL so multiple instances can share entries.- Cache keys include normalized search parameters: query, count,
lang,country,from,to, andsortBy. - Cache reads/writes are non-fatal for article searches. If the cache backend is unavailable, the service logs a warning, increments cache error metrics, falls through to GNews on read failure, and still returns the upstream response on write failure.
- Identical in-flight misses are coalesced per process, so concurrent requests for the same normalized search wait on one upstream provider request.
- Successful searches are also written to a longer-lived stale cache key. If a later fresh miss hits an upstream failure and stale data is available,
/api/v1/*returnsmeta.cache=staleandX-Cache-Status: stalewith a200response instead of surfacing the provider outage.
On shutdown the server closes the Redis connection when that backend was used.
GET /metrics exposes default Node.js process metrics plus application metrics:
| Metric | Labels | Purpose |
|---|---|---|
http_requests_total |
method, status_code |
HTTP response count. |
news_cache_events_total |
`result=hit | miss |
news_cache_errors_total |
`operation=get | set |
news_upstream_requests_total |
`outcome=success | error |
news_upstream_request_duration_seconds |
`outcome=success | error |
news_upstream_circuit_events_total |
`event=opened | short_circuit |
Use cache hit rate and coalesced miss counts to understand quota protection, stale counts to see when provider trouble is being hidden by cached data, cache error metrics to detect Redis/backend trouble, upstream latency/error metrics to separate provider trouble from local API trouble, and circuit events to see when repeated provider failures are being shed locally.
docker build -t news-api:local .
docker run --rm -p 3000:3000 -e GNEWS_API_KEY=your_key news-api:localOr with Compose (requires GNEWS_API_KEY in .env):
docker compose up --buildAfter the service is running and ready, run:
BASE_URL=http://localhost:3000 QUERY=postgres npm run smokeIf CLIENT_API_KEYS is configured, pass a matching client key:
CLIENT_API_KEY=client-secret-one npm run smokeThe smoke test checks /health, /ready, /openapi.yaml, /api/articles, and /metrics.
To smoke-test the production container without a live GNews key, boot the CI Compose stack against the fake provider:
npm run smoke:dockerFor hosted Docker deployment notes and safe public-demo defaults, see DEPLOYMENT.md.
For reproducible cache/upstream performance checks without a live GNews key:
npm run benchmark:localSee BENCHMARKS.md for methodology and tuning variables.
Logs are JSON (Pino). Each response includes an x-request-id header for correlation.
The process closes the HTTP server on SIGTERM / SIGINT before exit. Orchestrators should use termination grace periods longer than SHUTDOWN_TIMEOUT_MS.