Caching adalah cara termudah membuat API kamu jauh lebih cepat dan hemat resource. Kalau data tidak sering berubah, kenapa compute ulang untuk setiap request?
HTTP caching bekerja di 3 level: browser, CDN, server. Yang kamu kontrol via header response.
Cache-Control Header
Header paling penting. Bilang ke klien/proxy/CDN: "cache response ini, berapa lama, dan apakah public atau private?"
app.get("/api/products/:id", (req, res) => {
const product = getProduct(req.params.id);
// Cache 5 menit di browser + CDN
res.set("Cache-Control", "public, max-age=300");
res.json(product);
});
Directive yang wajib kamu tahu:
| Directive | Arti |
|---|---|
public |
Boleh di-cache siapapun (browser, CDN, proxy) |
private |
Hanya boleh cache browser user (untuk data per-user) |
no-cache |
Harus validasi ulang ke server sebelum dipakai (via ETag/Last-Modified) |
no-store |
Jangan cache sama sekali (data sensitif — bank, medical) |
max-age=N |
Cache valid N detik |
s-maxage=N |
Cache valid N detik untuk shared cache (CDN), override max-age |
immutable |
Data tidak akan berubah selama hidup cache (cocok untuk hash-named assets) |
stale-while-revalidate=N |
Serve stale response + refresh di background selama N detik |
Pola real-world:
// Static product catalog — aman cache lama
res.set("Cache-Control", "public, max-age=3600, s-maxage=86400");
// User profile sendiri — per-user, cache browser saja
res.set("Cache-Control", "private, max-age=60");
// Dashboard real-time — jangan cache
res.set("Cache-Control", "no-store");
// Boleh stale 5 menit saat refresh background
res.set("Cache-Control", "public, max-age=300, stale-while-revalidate=600");
ETag + Conditional Request — Cache Validation
Kadang data bisa di-cache tapi cache-time tidak diketahui. Solusi: ETag (identifier unik dari response). Klien kirim ETag di request berikutnya; kalau belum berubah, server return 304 Not Modified (tanpa body).
import crypto from "crypto";
app.get("/api/articles/:id", (req, res) => {
const article = getArticle(req.params.id);
const etag = crypto.createHash("md5").update(JSON.stringify(article)).digest("hex");
// Klien sudah punya versi ini?
if (req.headers["if-none-match"] === etag) {
return res.status(304).end(); // No body, hemat bandwidth
}
res.set("ETag", etag);
res.set("Cache-Control", "private, max-age=0, must-revalidate");
res.json(article);
});
Browser otomatis kirim If-None-Match: <etag> di request berikutnya. Kalau sama, server hemat compute body + network tidak perlu kirim bytes. Cocok untuk data yang jarang berubah tapi tidak bisa di-cache lama (misal: user profile yang kadang di-edit).
Last-Modified — Alternatif ETag
Mirip ETag tapi pakai timestamp:
app.get("/api/articles/:id", (req, res) => {
const article = getArticle(req.params.id);
const lastMod = new Date(article.updated_at).toUTCString();
if (req.headers["if-modified-since"] === lastMod) {
return res.status(304).end();
}
res.set("Last-Modified", lastMod);
res.json(article);
});
ETag lebih akurat (granularitas lebih tinggi), Last-Modified lebih mudah (pakai kolom updated_at yang sudah ada).
CDN Caching untuk API
CDN (Cloudflare, Fastly, AWS CloudFront) bisa cache API response di edge — user di Jakarta dapat response dari server CDN di Jakarta, bukan server asli di Singapore.
Syarat CDN cache:
- Response pakai
Cache-Control: public(ataus-maxagetanpaprivate) Vary: Accept-Encoding, Authorizationuntuk varian berdasarkan header- Status 200 GET only (POST/PUT/DELETE tidak di-cache)
// Response yang CDN-cacheable
res.set("Cache-Control", "public, s-maxage=300");
res.set("Vary", "Accept-Language, Authorization");
res.set("CDN-Cache-Control", "max-age=600"); // override untuk CDN saja (Cloudflare)
Cache Invalidation
"There are only two hard things in Computer Science: cache invalidation and naming things." — Phil Karlton
Strategi umum:
- Time-based (TTL) — paling simple; cache expired setelah N detik. OK untuk data yang boleh stale beberapa menit.
- Event-based purge — saat data berubah, kirim purge request ke CDN. Cloudflare:
POST /zones/{id}/purge_cache. Cepat tapi butuh koordinasi. - Versioned URL —
api/v2/productsvsapi/v1/products. Ganti version = cache baru. Mahal tapi 100% correctness. - Cache key dengan hash konten —
/assets/app.abc123.js. Berubah = nama baru = cache baru. Ideal untuk static assets.
Anti-Pattern Umum
❌ Cache response dengan Set-Cookie header di CDN — semua user dapat cookie user pertama yang hit. Keamanan breach. Selalu Cache-Control: private untuk response berisi cookie user.
❌ Cache error responses — 500 bisa transient, jangan cache 500 detik. Pakai Cache-Control: no-store untuk status 5xx.
❌ Lupa Vary: Authorization — CDN cache response user A dan kirim ke user B. Gunakan Vary untuk header yang mempengaruhi response.
Checklist
- Setiap GET endpoint punya strategy cache (public/private/no-store + max-age)
- Pakai ETag untuk endpoint yang jarang berubah
-
Vary: Authorizationdi response private - CDN di-test dengan
curl -I— lihat header X-Cache: HIT/MISS - Invalidation strategy jelas (TTL, purge, atau versioned)
- Response 4xx/5xx pakai
Cache-Control: no-store