Consuming Third-Party APIs
Almost every real Node service is also an API client — it calls Stripe to charge a card, sends mail through SendGrid, asks OpenAI for a completion, or talks to an internal microservice. Calling someone else's API over the network is fundamentally different from calling a local function: it's slow, fallible, and outside your control. The network drops, the remote service is down or rate-limits you, responses come back malformed. Treating remote calls with the respect that unreliability demands — timeouts, retries, error handling, secret management — is what separates a robust integration from one that takes your app down with it. This page covers the lifecycle of an outbound request and the production concerns that wrap it.
The anatomy of an outbound request
// A real third-party call has more concerns than just "fetch the URL":
async function getWeather(city) {
const res = await fetch(`https://api.weather.com/v1/current?city=${encodeURIComponent(city)}`, {
method: 'GET',
headers: {
Authorization: `Bearer ${process.env.WEATHER_API_KEY}`, // secret from env, never hard-coded
Accept: 'application/json',
},
signal: AbortSignal.timeout(5000), // 1. always set a timeout
})
if (!res.ok) { // 2. check the status yourself
throw new Error(`Weather API ${res.status}: ${await res.text()}`)
}
return res.json() // 3. parse the body
}The network is unreliable — design for failure
Concern | Why it matters | What to do |
|---|---|---|
Timeouts | A remote that never responds hangs your request forever, exhausting connections | Always set one ( |
Retries | Transient errors ( | Retry idempotent calls with exponential backoff + jitter |
Rate limits | APIs cap your request rate; exceeding it returns | Respect |
Circuit breaking | Hammering a down service wastes resources and slows recovery | Trip a breaker after repeated failures; fail fast for a cooldown |
Idempotency | A retried | Send an idempotency key; only auto-retry safe (idempotent) methods |
Managing secrets and keys
// ❌ NEVER hard-code credentials — they leak into git history forever:
const KEY = 'sk_live_51H8xQ2...'
// ✅ Read from the environment (set via .env locally, secrets manager in prod):
const KEY = process.env.STRIPE_SECRET_KEY
if (!KEY) throw new Error('STRIPE_SECRET_KEY is not set') // fail fast at startupHandle responses defensively
Check the status before parsing —
2xxis success;4xxis your mistake (bad request, auth, not found);5xxis theirs (retry candidates).Don't trust the body shape — a third party can change their response or return an error envelope; validate (e.g. with Zod) before using it.
Set a timeout on every call — there is no acceptable default of "wait forever".
Log failures with context — which API, status, and a correlation id — but redact secrets and PII from what you log.
Isolate the blast radius — wrap each integration so one flaky vendor degrades only its feature, not the whole request (timeouts, fallbacks, circuit breakers).
Cache when you can — many third-party responses (config, reference data) change rarely; cache them to cut latency, cost, and rate-limit pressure.