API Anda mungkin mengirimkan JSON yang sama ribuan kali sehari. Klien meminta `GET /v1/products/42`, mendapatkan 18 KB, meminta lagi lima menit kemudian, dan mendapatkan 18 KB yang sama. Tidak ada yang berubah. Anda tetap membayar bandwidth, serialisasi, dan pembacaan database. HTTP sudah menyelesaikan masalah ini. Header Cache-Control memberi tahu klien berapa lama respons tetap sesuai (fresh). Header ETag memberi mereka sidik jari untuk memeriksa apakah respons tersebut berubah. Bersama-sama, mereka mengubah permintaan berulang menjadi respons 304 Not Modified dengan badan kosong, dan sebagai bonus, mereka dapat melindungi penulisan Anda dari pembaruan yang hilang. Ide yang sama juga mendukung pola sisi klien; jika Anda sudah membaca panduan kami tentang [caching respons API di React](https://apidog.com/id/blog/caching-api-responses-in-react), ini adalah sisi server dari cerita tersebut. Panduan ini akan membahas tiga lapisan caching HTTP, menunjukkan perjalanan bolak-balik 304 selangkah demi selangkah, menjelaskan perbedaan no-cache vs no-store, dan diakhiri dengan kode Express yang berfungsi. Anda juga akan melihat cara memverifikasi semua ini di [Apidog](https://apidog.com) dengan mengirimkan header kondisional dan memastikan respons 304 sendiri.tombol
Tiga Lapisan Caching HTTP
Caching HTTP untuk API terbagi menjadi tiga keputusan terpisah. Tim mengalami masalah ketika mereka menggabungkannya.
Lapisan 1: Kesesuaian (Freshness). Berapa lama klien dapat menggunakan kembali respons tanpa perlu bertanya kepada Anda sama sekali? Itu adalah Cache-Control: max-age=60. Selama 60 detik, klien menyajikan salinan yang di-cache secara lokal. Tidak ada lalu lintas jaringan. Ini adalah hit cache termurah yang mungkin dan juga yang paling berisiko, karena klien tidak dapat mendeteksi perubahan sampai waktu habis.
Lapisan 2: Validasi. Setelah respons kedaluwarsa, klien tidak perlu mengunduhnya ulang. Klien bertanya "apakah ini berubah?" dengan mengirimkan sidik jari yang Anda berikan sebelumnya. Jika sumber daya tidak berubah, Anda menjawab dengan 304 Not Modified dan tanpa badan. ETag dengan If-None-Match adalah versi yang lebih presisi; Last-Modified dengan If-Modified-Since adalah versi lama berbasis stempel waktu dengan granularitas satu detik.
Lapisan 3: Pembatalan Validasi (Invalidation). Ketika data berubah, bagaimana salinan yang kedaluwarsa dibuang? Cache klien pribadi kedaluwarsa sendiri melalui max-age. Cache bersama dan CDN memerlukan penghapusan eksplisit, TTL singkat, atau direktif seperti stale-while-revalidate yang membatasi usia kedaluwarsa.
Kesesuaian menghemat paling banyak, validasi menangkap semua yang dilewatkan oleh kesesuaian, dan pembatalan validasi menjaga keduanya tetap jujur. Kebanyakan API membutuhkan ketiganya.
Cara Kerja Perjalanan Bolak-balik 304 Not Modified
Berikut adalah siklus lengkap untuk endpoint produk, langkah demi langkah.
Permintaan pertama. Klien tidak memiliki apa pun yang di-cache:
GET /v1/products/42 HTTP/1.1
Host: api.example.com
Respons pertama. Anda mengembalikan badan plus metadata caching:
HTTP/1.1 200 OK
Cache-Control: private, max-age=60
ETag: "33a64df551425fcc55e4d42a148795d9f2"
Content-Type: application/json
Content-Length: 18432
Klien menyimpan badan dan ETag. Selama 60 detik berikutnya klien tidak akan menghubungi Anda sama sekali.
Permintaan kedua, setelah 60 detik. Salinan sudah kedaluwarsa, jadi klien memvalidasi ulang:
GET /v1/products/42 HTTP/1.1
Host: api.example.com
If-None-Match: "33a64df551425fcc55e4d42a148795d9f2"
Respons kedua, sumber daya tidak berubah. Server Anda membandingkan ETag yang masuk dengan ETag saat ini. Keduanya cocok, jadi:
HTTP/1.1 304 Not Modified
Cache-Control: private, max-age=60
ETag: "33a64df551425fcc55e4d42a148795d9f2"
Tidak ada badan. Alih-alih 18 KB, responsnya adalah beberapa ratus byte header. Klien menandai salinan yang di-cache-nya sesuai (fresh) untuk 60 detik lagi dan menyajikannya. Jika produk telah berubah, Anda akan mengembalikan 200 normal dengan badan baru dan ETag baru. Kami membahas kode status itu sendiri lebih dalam di [penjelasan 304 Not Modified](https://apidog.com/id/blog/status-code-304-not-modified) kami; versi singkatnya adalah bahwa 304 adalah instruksi cache, bukan kesalahan.
Ekonominya sederhana. GET kondisional masih membutuhkan perjalanan bolak-balik ditambah pekerjaan apa pun yang menghitung ETag saat ini. Yang dihilangkan adalah transfer muatan (payload) dan penguraian ulang sisi klien. Untuk endpoint daftar besar yang di-polling oleh klien seluler, ini secara rutin memotong egress API sebesar 60 hingga 90 persen.
Direktif Cache-Control yang Penting untuk API
Cache-Control memiliki lebih dari selusin direktif. Untuk API JSON, lima di antaranya paling penting.
no-store vs no-cache. Ini adalah bug caching paling umum di API produksi, dan terjadi di kedua arah. no-store berarti "jangan pernah tulis ini ke cache apa pun." Gunakan ini untuk muatan yang benar-benar sensitif: token, data perbankan, PII yang tidak boleh Anda pertahankan. no-cache berarti hampir kebalikan dari yang kedengarannya: cache BOLEH menyimpan respons, tetapi mereka harus memvalidasi ulang dengan origin sebelum setiap penggunaan kembali. Dipasangkan dengan ETag, no-cache memberi Anda penghematan 304 pada setiap permintaan sambil menjamin klien tidak pernah menampilkan data yang kedaluwarsa. Tim yang asal menempelkan no-store pada semuanya "agar aman" berarti menonaktifkan permintaan kondisional sepenuhnya dan membayar biaya muatan penuh pada setiap panggilan.
private. Menandai respons sebagai dapat di-cache hanya oleh klien pengguna akhir, tidak pernah oleh cache bersama atau CDN. Setiap respons yang bervariasi per pengguna, yang merupakan sebagian besar lalu lintas API terautentikasi, harus menyertakan private. Tanpa itu, proxy yang salah konfigurasi dapat menyajikan data akun satu pengguna ke pengguna lain.
max-age. Masa berlaku kesesuaian dalam detik. Untuk API, pikirkan angka kecil: 30 hingga 300 detik mencakup sebagian besar endpoint baca. Anda tidak mencoba menghilangkan permintaan selama sehari; Anda mencoba menyerap ledakan dan loop polling.
stale-while-revalidate. Jalan tengah yang pragmatis. Cache-Control: max-age=60, stale-while-revalidate=300 memberi tahu cache: sajikan salinan yang kedaluwarsa hingga 5 menit tambahan, tetapi perbarui di latar belakang. Pengguna mendapatkan respons instan; origin Anda diperbarui sesaat kemudian. CDN seperti Cloudflare dan Fastly mendukungnya, begitu juga peramban.
Default yang masuk akal untuk endpoint baca terautentikasi terlihat seperti ini:
Cache-Control: private, max-age=60, stale-while-revalidate=120
ETag: "9f8b2c41aa73e0d5"
Spesifikasi perilaku lengkap ada di RFC 9111, yang menggantikan RFC 7234 sebagai dokumen caching HTTP definitif. Ketika CDN berperilaku dengan cara yang mengejutkan Anda, RFC itulah tempat jawabannya berada.
ETag Kuat vs Lemah
Sebuah ETag hadir dalam dua rasa, dan awalan W/ memisahkannya.
ETag kuat (ETag: "33a64df551425fcc") menjanjikan kesetaraan byte-demi-byte. Dua respons dengan ETag kuat yang sama adalah identik, yang membuat ETag kuat aman untuk permintaan rentang byte dan diperlukan untuk kontrol konkurensi dengan If-Match.
ETag lemah (ETag: W/"33a64df551425fcc") menjanjikan kesetaraan semantik. Byte-nya mungkin berbeda, mungkin urutan bidang berubah atau bidang stempel waktu berdetak, tetapi maknanya sama, sehingga cache dapat menyimpan salinannya.
Di mana ini menyulitkan Anda: middleware kompresi. Nginx dan beberapa kerangka kerja menulis ulang ETag kuat menjadi ETag lemah ketika mereka meng-gzip respons secara on-the-fly, karena byte terkompresi tidak lagi cocok dengan aslinya. Jika pemeriksaan konkurensi Anda secara misterius gagal di balik proxy, cari awalan W/ yang tidak ada saat server aplikasi Anda mengirim respons.
Default ke ETag kuat yang dihitung pada badan yang tidak terkompresi. Gunakan ETag lemah hanya jika Anda secara sadar menyajikan representasi varian dari data yang sama.
Membuat ETag: Hash Badan vs Kolom Versi
Dua strategi mendominasi, dan yang tepat tergantung pada di mana biaya berada.
Hash dari badan respons. Serialisasi respons, hash (MD5 atau SHA-1 baik-baik saja di sini; ini adalah sidik jari, bukan batas keamanan), dan kutip. Ini akurat secara konstruksi dan tidak memerlukan perubahan skema. Kekurangannya: Anda membangun respons lengkap pada setiap permintaan, termasuk 304. Anda menghemat bandwidth tetapi tidak komputasi atau beban database.
Kolom versi atau updated_at. Ambil ETag dari data yang dapat Anda ambil dengan murah: ETag: "42-v17" dari penghitung versi baris, atau hash dari updated_at. Sekarang permintaan kondisional membutuhkan satu pencarian terindeks alih-alih serialisasi penuh. Kekurangannya: versi harus diperbarui pada setiap perubahan yang memengaruhi respons, termasuk perubahan pada tabel yang digabungkan (joined tables). Jika terlewat satu, Anda akan menyajikan 304 yang kedaluwarsa, yang merupakan bug caching terburuk karena tidak terlihat.
Mulailah dengan hashing badan. Ini benar secara default. Pindahkan endpoint panas ke ETag berbasis versi ketika pembuatan profil menunjukkan biaya serialisasi menjadi penting.
ETag untuk Konkurensi Optimistis: If-Match dan 412
Sidik jari yang sama yang menghemat bandwidth pada pembacaan mencegah pembaruan yang hilang pada penulisan.
Masalah pembaruan yang hilang: dua admin memuat produk 42 pada waktu yang sama. Admin A mengubah harga dan menyimpannya. Admin B memperbaiki kesalahan ketik dan menyimpannya 30 detik kemudian, menimpa perubahan harga A dengan harga kedaluwarsa yang dimuat B. Tidak ada yang melihat kesalahan. Data salah secara diam-diam.
Perbaikannya adalah membuat setiap pembaruan bersyarat pada versi yang terakhir dilihat klien:
PUT /v1/products/42 HTTP/1.1
If-Match: "33a64df551425fcc55e4d42a148795d9f2"
Content-Type: application/json
Server membandingkan If-Match dengan ETag sumber daya saat ini. Cocok: terapkan pembaruan, kembalikan 200 dengan ETag baru. Tidak cocok, orang lain tiba lebih dulu: tolak dengan 412 Precondition Failed dan jangan sentuh data. Klien kemudian mengambil ulang, menerapkan kembali perubahannya pada versi yang sesuai (fresh), dan mencoba lagi. API yang ketat melangkah lebih jauh dan mengembalikan 428 Precondition Required pada setiap PUT yang menghilangkan If-Match, membuat pemeriksaan keamanan menjadi wajib.
Ini hampir tidak memakan biaya untuk ditambahkan setelah ETag ada, dan mengubah bug korupsi data yang diam menjadi status HTTP eksplisit yang dapat dicoba lagi.
Apa yang Dilakukan CDN dan Proxy dengan Header Ini
Cache bersama berada di antara origin Anda dan klien Anda, dan mereka membaca header yang sama dengan aturan mereka sendiri.
privatemengecualikan respons dari caching CDN sepenuhnya;s-maxage=600menetapkan TTL spesifik CDN yang lebih panjang atau lebih pendek dari max-age peramban.- Sebagian besar CDN memvalidasi ulang dengan origin Anda menggunakan permintaan kondisional. Jika origin Anda menjawab If-None-Match dengan 304, CDN menyegarkan salinan yang disimpannya tanpa menarik badan. ETag juga membuat CDN Anda lebih murah.
- Selalu konfirmasikan kerangka kerja Anda mengirim
Varydengan benar. Sebuah API yang menyajikan JSON dan CSV dari satu URL membutuhkanVary: Accept, atau cache bersama akan menyerahkan CSV kepada klien JSON. - Perhatikan proxy yang melemahkan ETag melalui kompresi, seperti yang dibahas di atas.
Contoh Express: Mengembalikan ETag dan Menangani If-None-Match
Express mengatur ETag lemah secara otomatis, tetapi penanganan manual memberi Anda ETag kuat plus jalur penulisan 412:
import crypto from "node:crypto";
import express from "express";
const app = express();
app.use(express.json());
function etagFor(payload) {
const hash = crypto.createHash("sha1")
.update(JSON.stringify(payload))
.digest("hex");
return `"${hash}"`;
}
app.get("/v1/products/:id", async (req, res) => {
const product = await db.products.find(req.params.id);
const etag = etagFor(product);
res.set("Cache-Control", "private, max-age=60, stale-while-revalidate=120");
res.set("ETag", etag);
if (req.get("If-None-Match") === etag) {
return res.status(304).end(); // fingerprint matches: no body
}
res.json(product);
});
app.put("/v1/products/:id", async (req, res) => {
const product = await db.products.find(req.params.id);
const currentEtag = etagFor(product);
const ifMatch = req.get("If-Match");
if (!ifMatch) {
return res.status(428).json({ error: "If-Match header required" });
}
if (ifMatch !== currentEtag) {
return res.status(412).json({ error: "Resource changed since you fetched it" });
}
const updated = await db.products.update(req.params.id, req.body);
res.set("ETag", etagFor(updated));
res.json(updated);
});
Perhatikan bahwa cabang 304 masih mengirimkan header Cache-Control dan ETag. Sesuai RFC 9111, 304 memperbarui metadata respons yang disimpan, jadi kirim ulang apa pun yang dibutuhkan klien untuk menjaga salinannya tetap sesuai (fresh).
Memverifikasi Perilaku Caching di Apidog
Kode yang terlihat benar masih bisa melakukan caching yang salah setelah middleware dan proxy terlibat. Uji pada tingkat HTTP, bukan tingkat kode.
Di [Apidog](https://apidog.com), pemeriksaan manual membutuhkan waktu sekitar satu menit:
- Kirim
GET /v1/products/42dan buka panel header respons. KonfirmasikanETagdanCache-Controlada dan ETag dikutip. Salin nilai ETag. - Pada permintaan yang sama, tambahkan header
If-None-Matchdengan nilai yang disalin dan kirim lagi. Anda harus mendapatkan 304 dengan badan kosong. Jika Anda masih mendapatkan 200, lapisan validasi Anda tidak membandingkan sidik jari. - Ubah catatan, kirim ulang, dan konfirmasikan Anda kembali ke 200 dengan ETag yang sesuai (fresh).
Untuk menjaga ini berfungsi setelah setiap deploy, sambungkan alur yang sama ke skenario pengujian. Rangkaikan dua permintaan: yang pertama mengekstrak ETag dari header respons ke dalam variabel, yang kedua mengirimkannya kembali sebagai If-None-Match dan memastikan statusnya sama dengan 304 dan badannya kosong. Tambahkan langkah ketiga untuk jalur penulisan: kirim PUT dengan nilai If-Match yang sengaja kedaluwarsa seperti "deadbeefcafe1234" dan pastikan 412. Panduan kami tentang [API assertions](https://apidog.com/id/blog/api-assertions) mencakup sintaks assertion untuk kode status dan header.
Jalankan skenario itu di CI dan pembaruan middleware yang secara diam-diam menghilangkan ETag Anda akan menjadi pipeline yang gagal alih-alih tagihan bandwidth. [Unduh Apidog](https://apidog.com/download) secara gratis dan bangun skenario terhadap endpoint Anda sendiri; lebih lama membaca tentangnya daripada mengklik bersama.
FAQ
Apa perbedaan antara no-cache dan no-store?
no-store melarang caching sepenuhnya: tidak ada yang ditulis ke disk atau memori, jadi setiap permintaan mengunduh respons lengkap. no-cache memungkinkan penyimpanan tetapi memaksa validasi ulang sebelum setiap penggunaan kembali, jadi dipasangkan dengan ETag masih menghasilkan respons 304 dan penghematan muatan. Gunakan no-store hanya untuk data sensitif. Menggunakannya di mana-mana adalah kesalahan Cache-Control paling mahal yang dapat dilakukan tim API.
Apakah ETag berfungsi dengan POST?
Sebagian besar tidak, dan memang disengaja. ETag menggambarkan keadaan sumber daya di URL, dan POST biasanya membuat sesuatu yang baru daripada membaca keadaan stabil. Cache tidak menyimpan respons POST dalam praktiknya. Header kondisional yang penting untuk penulisan adalah If-Match pada PUT, PATCH, dan DELETE, di mana ETag menjaga dari pembaruan yang hilang. Jika Anda tergoda untuk menyimpan respons POST, itu biasanya merupakan tanda bahwa operasi tersebut seharusnya adalah GET.
Apakah respons 304 membuat API saya lebih cepat?
Itu membuat transfer lebih kecil, yang tidak sama. Server masih menerima permintaan, menjalankan otentikasi, dan menghitung ETag saat ini, jadi penghematan CPU origin bergantung pada seberapa murah Anda mendapatkan sidik jari itu. Kemenangan muncul dalam bandwidth, baterai seluler, dan waktu rendering pada jaringan lambat. Ukur sebelum dan sesudah; [panduan pengujian kinerja API](https://apidog.com/id/blog/api-performance-testing-tutorial) kami menunjukkan cara menguji latensi dan throughput sehingga Anda dapat membuktikan perbedaannya daripada menebak.
Haruskah saya menggunakan ETag atau Last-Modified?
Kirim keduanya jika Anda bisa. ETag lebih presisi: ia menangkap perubahan sub-detik dan perbedaan tingkat konten yang dilewatkan oleh stempel waktu, dan If-None-Match lebih diutamakan daripada If-Modified-Since ketika keduanya tiba. Last-Modified tetap berguna sebagai fallback untuk klien yang lebih lama dan sebagai heuristik yang digunakan beberapa cache untuk memperkirakan kesesuaian. Jika Anda hanya mengirim satu, kirim ETag.
