Build the mental model
This capstone puts every reliability piece from this course into one pipeline: your frontend needs data from a third-party API, but the secret key must never reach the browser, so your backend sits in the middle as a proxy.
Secret management means the credential lives in the backend's environment and gets attached server-side -- and never appears in a log line even when something goes wrong.
Response validation means a 200 status alone is not proof of success -- the body's shape must be checked before you trust it, since a misconfigured third party can return 200 with a malformed payload.
Timeouts, retries, and caching
Handle a transient failure with a bounded retry and backoff, giving the third party time to recover without hammering it. If a fresh, validated answer is already cached, skip the network call entirely.
- Check cache first.
- Add the credential only for a real network call.
- Validate the response before caching it.
- Retry only transient failures, never a validation failure.
THIRD-PARTY INTEGRATION PIPELINE
--------------------------------
Frontend
|
| request (e.g. GET /exchange-rate)
v
Backend:
1. Check cache -- hit --> skip straight to step 6
2. Attach secret credential (value never logged)
3. Call third-party API
4. Validate response -- invalid --> return error, do not cache
5. Transient failure? -- yes --> backoff, retry (max 3 attempts)
-- no --> continue
6. Store result in cache, return clean result
|
v
Frontend (gets clean result, secret never exposed)Connect it to a real scenario
Check the cache
Compute a cache key from the request and check an in-memory cache with a TTL -- a fresh hit returns immediately.
Attach the credential
Only on a cache miss, attach the credential to a copy of the request -- never let the secret's value reach a log.
Call with a bounded retry
Retry up to three attempts, doubling the backoff delay after each transient failure.
Validate the response
Run a non-transient response through validateResponse -- an invalid one returns an error immediately and is never cached.
Store and return
Only a response that succeeds and validates gets written to the cache and returned.
Read the log array
Reading each scenario's log shows exactly which stage ran and why -- the same way you would debug this in production.
Try the working example
// A deterministic stand-in for setTimeout, so backoff delays are
// simulated (logged) instead of actually pausing the test run.
function fakeDelay(ms, log) {
log.push(` waited ${ms}ms (backoff)`);
}
const cache = new Map();
const CACHE_TTL_MS = 60000;
// Never log the secret itself -- only that a credential was attached.
function addCredential(request, secretStore) {
const secret = secretStore.THIRD_PARTY_KEY;
return { ...request, headers: { ...request.headers, Authorization: `Bearer ${secret}` } };
}
function validateResponse(res) {
if (typeof res.status !== 'number') return { valid: false, reason: 'missing_status' };
if (res.status >= 200 && res.status < 300) {
if (!res.body || typeof res.body.total !== 'number') {
return { valid: false, reason: 'malformed_body' };
}
return { valid: true };
}
return { valid: false, reason: `bad_status_${res.status}` };
}
function fetchWithReliability(request, mockApiCall, secretStore, now = Date.now()) {
const log = [];
const cacheKey = `${request.method} ${request.path}`;
const cached = cache.get(cacheKey);
if (cached && now - cached.storedAt < CACHE_TTL_MS) {
log.push('cache hit, skipping network call');
return { ok: true, source: 'cache', data: cached.data, log };
}
log.push('cache miss');
const signedRequest = addCredential(request, secretStore);
log.push('attached credential (value not logged)');
const maxAttempts = 3;
let lastError = null;
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
log.push(`attempt ${attempt}: calling third-party API`);
const res = mockApiCall(signedRequest, attempt);
if (res.transientError) {
lastError = 'transient_error';
log.push(`attempt ${attempt} failed with a transient error`);
if (attempt < maxAttempts) {
fakeDelay(2 ** attempt * 100, log);
continue;
}
break;
}
const check = validateResponse(res);
if (!check.valid) {
log.push(`attempt ${attempt} returned an invalid response: ${check.reason}`);
return { ok: false, source: 'network', error: check.reason, log };
}
cache.set(cacheKey, { data: res.body, storedAt: now });
log.push('response validated and cached');
return { ok: true, source: 'network', data: res.body, log };
}
return { ok: false, source: 'network', error: lastError, log };
}
// --- Fixed, deterministic scenarios -------------------------------------
const secretStore = { THIRD_PARTY_KEY: 'sk_live_should_never_be_logged' };
const request = { method: 'GET', path: '/exchange-rate', headers: {} };
console.log('Scenario 1: first-time call succeeds immediately');
let callCount = 0;
const alwaysSucceeds = () => {
callCount++;
return { status: 200, body: { total: 1 } };
};
console.log(fetchWithReliability(request, alwaysSucceeds, secretStore, 1000));
console.log('\nScenario 2: cache hit (same request, 5 seconds later)');
console.log(fetchWithReliability(request, alwaysSucceeds, secretStore, 6000));
console.log('\nScenario 3: transient failure on attempt 1, succeeds on attempt 2');
const differentRequest = { method: 'GET', path: '/inventory-count', headers: {} };
const flakyOnce = (req, attempt) => {
if (attempt === 1) return { transientError: true };
return { status: 200, body: { total: 42 } };
};
console.log(fetchWithReliability(differentRequest, flakyOnce, secretStore, 100000));Scenario 1 (first-time success) returns { ok: true, source: 'network', data: { total: 1 } } with a log showing cache miss, credential attached, attempt 1, and 'response validated and cached'. Scenario 2 (repeat request) returns { ok: true, source: 'cache', data: { total: 1 } } with only 'cache hit, skipping network call' in the log -- no credential is attached and no network call happens. Scenario 3 (transient failure then success) returns { ok: true, source: 'network', data: { total: 42 } } with a log showing attempt 1 failing, a 200ms backoff wait, then attempt 2 succeeding.5-minute try-it
Add a maxAttempts parameter and a scenario where every attempt fails with a transient error -- confirm fetchWithReliability gives up after 3 attempts and returns { ok: false, error: 'transient_error' } instead of retrying forever.
One important caution
Caching a response before validating it -- a malformed or error payload gets served from cache to every subsequent request until the TTL expires.
Retrying on every kind of failure, including validation failures -- retrying bad data wastes attempts and time without ever producing a different result.
AWS Architecture Blog: Exponential Backoff and Jitter — API Integration & Webhooks