A free VIN decode proxy that always re-fetches DecodeVinValues for the same normalized VIN wastes NHTSA quota on identical payloads. Idempotency keys (covered elsewhere) collapse retries of one button press. Stale-response guards (also covered elsewhere) drop late arrivals after a newer VIN wins. This post is a different lever: an HTTP cache with ETags so a repeat lookup for the same VIN can answer 304 Not Modified (or serve a fresh cached body) without another upstream call.
The goal is narrow: store a decode body keyed by normalized VIN, emit an ETag, honor If-None-Match, and skip NHTSA when the representation is unchanged. Do not confuse this with client idempotency keys, debounce on partial digits, or "cache forever without revalidation."
Where unconditional fetches hurt
Every successful DecodeVinValues costs a free-quota unit. Unconditional re-decode fails when traffic repeats:
- A user toggles tabs and re-submits the same VIN minutes later
- A BFF refreshes a card that already holds yesterday's catalog snapshot
- Monitoring probes the same golden VIN on a schedule
- Two tabs race the same normalized VIN after a page reload
Each case is a representation-cache problem. Idempotency protects one attempt's retries; ETag caching protects repeat intents for the same VIN while still allowing deliberate revalidation.
Shape the cache entry
Key on the normalized VIN (and optional decode profile). Store the JSON body, a strong ETag, and a stored-at timestamp for TTL decisions.
export type DecodeCacheEntry = {
vinNormalized: string;
body: string;
etag: string;
storedAt: number;
maxAgeMs: number;
};
export type DecodeCache = Map<string, DecodeCacheEntry>;
export function makeEtag(body: string): string {
// Strong validator from body bytes; swap for sha256 in production.
let h = 0;
for (let i = 0; i < body.length; i++) {
h = (Math.imul(31, h) + body.charCodeAt(i)) | 0;
}
return `"vin-${(h >>> 0).toString(16)}"`;
}
export function putDecode(
cache: DecodeCache,
vinNormalized: string,
body: string,
now = Date.now(),
maxAgeMs = 86_400_000,
): DecodeCacheEntry {
const entry: DecodeCacheEntry = {
vinNormalized,
body,
etag: makeEtag(body),
storedAt: now,
maxAgeMs,
};
cache.set(vinNormalized, entry);
return entry;
}
export function isFresh(entry: DecodeCacheEntry, now = Date.now()): boolean {
return now - entry.storedAt < entry.maxAgeMs;
}
The ETag is a validator for the cached representation, not a client attempt id. A new upstream body that differs produces a new ETag; an unchanged body keeps the same validator.
Honor If-None-Match before upstream
On a decode request, check the cache first. If the entry is fresh and the client's If-None-Match matches, return 304 with no body and no NHTSA call. If fresh without a matching precondition, return 200 with the cached body. Only miss or stale entries reach DecodeVinValues.
export type CacheResult =
| { status: 304; etag: string }
| { status: 200; body: string; etag: string; fromCache: boolean };
export async function decodeWithEtag(opts: {
cache: DecodeCache;
vinNormalized: string;
ifNoneMatch?: string | null;
fetchUpstream: (vin: string) => Promise<string>;
now?: number;
}): Promise<CacheResult> {
const now = opts.now ?? Date.now();
const hit = opts.cache.get(opts.vinNormalized);
if (hit && isFresh(hit, now)) {
if (opts.ifNoneMatch && opts.ifNoneMatch === hit.etag) {
return { status: 304, etag: hit.etag };
}
return {
status: 200,
body: hit.body,
etag: hit.etag,
fromCache: true,
};
}
const body = await opts.fetchUpstream(opts.vinNormalized);
const entry = putDecode(opts.cache, opts.vinNormalized, body, now);
return {
status: 200,
body: entry.body,
etag: entry.etag,
fromCache: false,
};
}
Wire ETag and Cache-Control on the HTTP response. Clients that store the ETag can send If-None-Match on the next lookup and skip payload transfer when nothing changed. Prefer a modest max-age (hours to a day for a free decode card), not forever.
Normalize the VIN before the cache key (uppercase, strip separators) so casing variants share one entry. Include profile or format in the key if your proxy returns different field subsets for the same VIN.
What this is not
Product pressure often asks for:
- Treating ETag like an idempotency key for double-clicks (wrong layer)
- Caching forever with no TTL so stale catalog rows never refresh
- Keying only on raw user input so
1HGCM...and1hgcm...miss each other - Returning 304 when the body actually changed upstream after TTL expiry
- Skipping NHTSA on every hit even when the operator requested a forced refresh
Refuse those. Support an explicit Cache-Control: no-cache or ?refresh=1 path that bypasses freshness and revalidates. Keep ETag caching distinct from debounce, hedges, and attempt keys.
export function shouldBypassCache(headers: {
cacheControl?: string | null;
refresh?: string | null;
}): boolean {
const cc = (headers.cacheControl ?? "").toLowerCase();
if (cc.includes("no-cache") || cc.includes("no-store")) return true;
return headers.refresh === "1" || headers.refresh === "true";
}
Quick checks
import assert from "node:assert/strict";
const cache: DecodeCache = new Map();
let upstreamCalls = 0;
const fetchUpstream = async (vin: string) => {
upstreamCalls += 1;
return JSON.stringify({ VIN: vin, Make: "HONDA" });
};
const first = await decodeWithEtag({
cache,
vinNormalized: "1HGCM82633A004352",
fetchUpstream,
});
assert.equal(first.status, 200);
assert.equal(first.fromCache, false);
assert.equal(upstreamCalls, 1);
const second = await decodeWithEtag({
cache,
vinNormalized: "1HGCM82633A004352",
ifNoneMatch: first.etag,
fetchUpstream,
});
assert.equal(second.status, 304);
assert.equal(upstreamCalls, 1);
const third = await decodeWithEtag({
cache,
vinNormalized: "1HGCM82633A004352",
fetchUpstream,
});
assert.equal(third.status, 200);
assert.equal(third.fromCache, true);
assert.equal(upstreamCalls, 1);
Review rule: ETag modules must skip upstream on fresh matching validators, revalidate after TTL, and never pretend an attempt key is a representation validator.
Takeaway
ETag caching lets repeat VIN lookups reuse a fresh DecodeVinValues representation and answer 304 when the client already holds that validator. Pair a VIN-keyed store, a strong ETag, and a modest TTL. Keep this separate from idempotency keys and input debounce. Offer a forced-refresh path for operators who need a live upstream read. Your free proxy stays kind to NHTSA quota when identical catalog snapshots stop costing a second upstream call.
I maintain VIN Lookup, a free VIN decode based on NHTSA data.
Top comments (0)