ETag dan Conditional Request: Kuasai HTTP Caching untuk API
Daftar Isi
- Pendahuluan
- Apa Itu ETag?
- Mengapa Menggunakan ETag dalam API?
- Cara Kerja ETag
- Strong vs Weak ETag
- Header Conditional Request
- Contoh Praktis: Product API
- Use Case ETag dalam Testing API
- Best Practice
- ETag di Framework Populer
- FAQ
Pendahuluan
API yang efisien memerlukan strategi caching yang cerdas untuk memberikan respons cepat dan meminimalkan penggunaan jaringan yang tidak perlu. Salah satu tool paling powerful di toolkit developer API adalah header ETag (Entity Tag). ETag memungkinkan cache validation, mengurangi konsumsi bandwidth, dan memastikan konsistensi data antara klien dan server.
Di 2026, dengan pertumbuhan eksplosif aplikasi mobile dan real-time API, memahami ETag sangat penting untuk membangun sistem yang scalable dan performan. Panduan ini mencakup semua yang perlu Kamu ketahui tentang mengimplementasikan dan menggunakan ETag secara efektif.
Apa Itu ETag?
ETag (Entity Tag) adalah header respons HTTP yang secara unik mengidentifikasi versi spesifik dari resource di server. Pada dasarnya, ETag adalah sidik jari atau hash dari state resource saat ini.
HTTP/1.1 200 OK
Content-Type: application/json
ETag: "abc123def456"
{
"id": 1,
"name": "Wireless Headphones",
"price": 99.99
}
Setiap kali resource berubah, server menghasilkan nilai ETag yang baru. Ini memungkinkan klien untuk mengetahui apakah versi cache mereka masih valid atau sudah outdated tanpa perlu men-download ulang seluruh resource.
Karakteristik Utama
- Version Identifier: Setiap ETag mewakili versi unik dari resource
- Server-Generated: Hanya server yang menentukan nilai ETag
- Immutable: Untuk state resource tertentu, ETag tetap sama
- Lightweight: Biasanya string pendek (hash, MD5, timestamp, dll.)
Mengapa Menggunakan ETag dalam API?
ETag membawa nilai signifikan untuk desain API modern:
Mengurangi Konsumsi Bandwidth
Alih-alih men-download seluruh resource lagi, klien dapat mengirimkan conditional request. Jika resource tidak berubah, server merespons dengan 304 Not Modified tanpa response body.
GET /api/products/101
If-None-Match: "abc123def456"
HTTP/1.1 304 Not Modified
Ini menghemat bandwidth yang substantial, terutama untuk resource besar.
Performa Lebih Cepat
Dengan meminimalkan ukuran payload dan response time, ETag meningkatkan user experience:
- Latency berkurang untuk resource yang tidak berubah
- Bandwidth lebih rendah = respons keseluruhan lebih cepat
- Byte lebih sedikit dikirim = halaman load cepat di mobile
Kontrol Konkurensi
ETag mencegah conflicting update dalam skenario multi-klien menggunakan header If-Match:
PUT /api/products/101
If-Match: "abc123def456"
Content-Type: application/json
{ "price": 89.99 }
Jika ETag tidak match (resource diupdate oleh klien lain), server mengembalikan 412 Precondition Failed, mencegah overwrite conflict.
Konsistensi Data
ETag memastikan klien selalu bekerja dengan versi resource yang benar, mencegah masalah stale-data.
Cara Kerja ETag
Alur kerja ETag mengikuti pola sederhana tapi powerful:
Langkah 1: Server Menghasilkan ETag
Klien membuat request awal:
GET /api/users/1
Server merespons dengan resource dan ETag:
HTTP/1.1 200 OK
ETag: "user-v1-abc123"
Content-Type: application/json
{
"id": 1,
"name": "John Doe",
"email": "[email protected]"
}
Langkah 2: Klien Melakukan Caching
Klien menyimpan data resource dan nilai ETag di memori lokal.
Langkah 3: Conditional Request
Untuk request selanjutnya, klien menyertakan ETag menggunakan header If-None-Match:
GET /api/users/1
If-None-Match: "user-v1-abc123"
Langkah 4: Validasi Server
Server memeriksa apakah resource telah berubah:
Jika tidak berubah:
HTTP/1.1 304 Not Modified
Jika berubah:
HTTP/1.1 200 OK
ETag: "user-v1-def789"
Content-Type: application/json
{
"id": 1,
"name": "John Doe",
"email": "[email protected]"
}
Strong vs Weak ETag
Strong ETag
Strong ETag adalah representasi byte-for-byte dari resource, dihasilkan menggunakan hash cryptographic:
ETag: "33a64df551425fcc55e4d42a148795d9f25f89d4"
Use case:
- Respons API di mana exact content matching penting
- Resource yang harus byte-identical untuk validation
- Kontrol konkurensi dengan If-Match
Trade-off: Lebih mahal secara computational untuk dihasilkan
Weak ETag
Weak ETag mewakili semantic equivalence daripada byte-for-byte identity, diberi prefix W/:
ETag: W/"0815"
Use case:
- Resource besar di mana perbedaan formatting kecil tidak penting
- Content yang di-generate secara dinamis
- Mengurangi overhead komputasi server
Keuntungan: Komputasi lebih cepat, cocok untuk API traffic tinggi
Keterbatasan: Tidak bisa digunakan dengan If-Match untuk kontrol konkurensi
Header Conditional Request
If-None-Match
Dikirim oleh klien untuk mengecek apakah resource telah berubah. Server merespons dengan 304 jika ETag cocok (resource tidak berubah).
GET /api/products/101
If-None-Match: "abc123", "abc124"
Server dapat membandingkan terhadap beberapa ETag.
If-Match
Digunakan untuk pembaruan yang aman. Server hanya memproses request jika ETag cocok (resource belum dimodifikasi oleh pihak lain).
PUT /api/products/101
If-Match: "abc123"
Content-Type: application/json
{ "stock": 50 }
Response jika ETag tidak match:
HTTP/1.1 412 Precondition Failed
Contoh Praktis: Product API
Berikut contoh lengkap implementasi ETag support dalam Node.js/Express API:
const express = require('express');
const crypto = require('crypto');
const app = express();
// In-memory data store
const products = {
101: {
id: 101,
name: "Wireless Headphones",
price: 120,
stock: 45
}
};
// Generate ETag dari product data
function generateETag(product) {
const hash = crypto.createHash('md5');
hash.update(JSON.stringify(product));
return hash.digest('hex');
}
// GET dengan ETag
app.get('/api/products/:id', (req, res) => {
const product = products[req.params.id];
if (!product) {
return res.status(404).json({ error: 'Tidak ditemukan' });
}
const etag = generateETag(product);
res.set('ETag', `"${etag}"`);
// Periksa header If-None-Match
if (req.get('If-None-Match') === `"${etag}"`) {
return res.status(304).end();
}
res.json(product);
});
// PUT dengan If-Match untuk safe update
app.put('/api/products/:id', express.json(), (req, res) => {
const product = products[req.params.id];
if (!product) {
return res.status(404).json({ error: 'Tidak ditemukan' });
}
const currentETag = generateETag(product);
const ifMatch = req.get('If-Match');
// Periksa konkurensi
if (ifMatch && ifMatch !== `"${currentETag}"`) {
return res.status(412).json({
error: 'Precondition failed',
message: 'Resource dimodifikasi oleh klien lain'
});
}
// Update product
Object.assign(product, req.body);
const newETag = generateETag(product);
res.set('ETag', `"${newETag}"`);
res.json(product);
});
app.listen(3000, () => console.log('API berjalan di port 3000'));
Use Case ETag dalam Testing API
ETag sangat valuable untuk comprehensive API testing:
Validasi Cache
Verifikasi bahwa klien mengirim If-None-Match header yang valid dan menerima respons 304 tanpa body.
Verifikasi Update Data
Konfirmasi bahwa ETag berubah setiap kali underlying resource diupdate, menunjukkan bahwa versi cached sudah kadaluarsa.
Pengujian Konkurensi
Validasi bahwa API menolak update saat klien menggunakan ETag yang kadaluarsa, mencegah kehilangan data dari race condition.
Validasi Performa
Bandingkan ukuran payload dan response time dengan ETag diaktifkan vs. dinonaktifkan untuk mengukur penghematan bandwidth.
Penanganan Header
Verifikasi implementasi If-None-Match dan If-Match dengan benar untuk komunikasi klien-server yang dapat diandalkan.
Best Practice
1. Gunakan Strong ETags Saat Precision Matters
Hasilkan strong ETag berdasarkan exact resource content saat byte-level validation critical:
const etag = crypto.createHash('sha256')
.update(JSON.stringify(resource))
.digest('hex');
2. Manfaatkan Weak ETags untuk Resource Besar
Untuk high-traffic API yang melayani large atau frequently-changing resource, weak ETag mengurangi komputasi:
res.set('ETag', `W/"${resourceVersion}"`);
3. Regenerasi ETag pada Setiap Update
Selalu hasilkan ETag baru saat resource berubah, memastikan klien bekerja dengan versi terbaru.
4. Dukung Conditional Request
Implementasikan kedua header If-None-Match dan If-Match di API Kamu:
// Cache validation
if (req.get('If-None-Match') === currentETag) {
res.status(304).end();
}
// Kontrol konkurensi
if (req.get('If-Match') && req.get('If-Match') !== currentETag) {
res.status(412).json({ error: 'Conflict' });
}
5. Kombinasikan dengan Caching Header Lain
Gunakan ETag bersama Cache-Control dan Last-Modified untuk robust caching:
res.set('ETag', `"${etag}"`);
res.set('Cache-Control', 'public, max-age=3600');
res.set('Last-Modified', new Date(resource.updatedAt).toUTCString());
6. Hindari Expensive ETag Generation
Balance accuracy dengan performa. Untuk frequently-accessed resource, pertimbangkan:
- Menggunakan weak ETag
- Caching nilai ETag
- Computing ETag secara async
7. Dokumentasikan Perilaku ETag
Di API documentation Kamu, spesifikkan dengan jelas:
- Endpoint mana yang support ETag
- Apakah ETag strong atau weak
- Expected behavior dengan conditional request
ETag di Framework Populer
Spring Boot
import org.springframework.web.bind.annotation.*;
import org.springframework.http.ResponseEntity;
import org.springframework.http.HttpHeaders;
import java.security.MessageDigest;
import java.util.Base64;
@RestController
@RequestMapping("/api")
public class ResourceController {
private String generateETag(String content) throws Exception {
MessageDigest md = MessageDigest.getInstance("SHA-256");
byte[] messageDigest = md.digest(content.getBytes());
return Base64.getEncoder().encodeToString(messageDigest);
}
@GetMapping("/resource")
public ResponseEntity<?> getResource(
@RequestHeader(value = "If-None-Match", required = false) String ifNoneMatch)
throws Exception {
String data = "{\"id\": 1, \"name\": \"Example\"}";
String etagValue = generateETag(data);
if (ifNoneMatch != null && ifNoneMatch.equals("\"" + etagValue + "\"")) {
return ResponseEntity.status(304).build();
}
return ResponseEntity.ok()
.header(HttpHeaders.ETAG, "\"" + etagValue + "\"")
.body(data);
}
@PutMapping("/resource")
public ResponseEntity<?> updateResource(
@RequestHeader(value = "If-Match", required = false) String ifMatch,
@RequestBody Map<String, Object> data) throws Exception {
String currentETag = generateETag(getCurrentResourceData());
if (ifMatch != null && !ifMatch.equals("\"" + currentETag + "\"")) {
return ResponseEntity.status(412)
.body(new ErrorResponse("Precondition Failed",
"Resource telah dimodifikasi oleh klien lain"));
}
// Update resource
String newETag = generateETag(data.toString());
return ResponseEntity.ok()
.header(HttpHeaders.ETAG, "\"" + newETag + "\"")
.body(data);
}
}
Spring Boot menyediakan dukungan ETag built-in melalui ShallowEtagHeaderFilter untuk caching otomatis pada level filter.
Express.js
const etag = require('etag');
app.get('/api/resource', (req, res) => {
const data = { /* ... */ };
const etagValue = etag(JSON.stringify(data));
res.set('ETag', etagValue);
if (req.get('If-None-Match') === etagValue) {
res.status(304).end();
} else {
res.json(data);
}
});
Django
from django.views.decorators.http import condition
def resource_etag(request):
return generate_etag_for_resource()
@condition(etag_func=resource_etag)
def get_resource(request):
return JsonResponse(resource_data)
Django secara otomatis menangani respons 304 saat ETag cocok.
FastAPI
from fastapi import FastAPI, Request
from fastapi.responses import JSONResponse
import hashlib
app = FastAPI()
def calculate_etag(data: str) -> str:
return hashlib.md5(data.encode()).hexdigest()
@app.get("/api/resource")
async def get_resource(request: Request):
data = {"id": 1, "name": "Example"}
etag_value = calculate_etag(str(data))
if request.headers.get("If-None-Match") == f'"{etag_value}"':
return JSONResponse(status_code=304, content=None)
response = JSONResponse(content=data)
response.headers["ETag"] = f'"{etag_value}"'
return response
FAQ
Q: Apakah ETag sama dengan header Last-Modified?
A: Tidak. Last-Modified menggunakan timestamp, yang bisa kurang presisi. ETag menyediakan exact content matching dan lebih reliable untuk kontrol konkurensi.
Q: Bisakah saya gunakan ETag dan Last-Modified header bersamaan?
A: Ya. Menggunakan keduanya memberikan defense in depth. Klien dapat menggunakan metode mana pun yang didukung server.
Q: Apa yang terjadi jika saya mengubah algoritma ETag generation?
A: Existing cached ETag menjadi invalid. Klien akan re-fetch resource. Rencanakan perubahan algoritma dengan hati-hati untuk menghindari performance hit.
Q: Apakah ETag aman?
A: ETag tidak menyediakan keamanan. Mereka terlihat di HTTP response dan request. Jangan sertakan informasi sensitif di ETag; gunakan murni untuk validasi cache.
Q: Bagaimana menangani ETag di sistem terdistribusi?
A: Pastikan semua server menghasilkan ETag yang sama untuk resource yang identik. Gunakan caching terpusat atau algoritma consistent hashing di seluruh cluster.
Q: Apakah browser cache menghormati ETag?
A: Ya. Browser modern secara otomatis mengirim header If-None-Match untuk resource yang di-cache dan menghormati respons 304.
Q: Berapa dampak performa dari pembuatan ETag?
A: Minimal jika menggunakan weak ETag atau hash yang sudah dihitung sebelumnya. Strong ETag memerlukan lebih banyak komputasi tetapi masih dapat diabaikan di hardware modern.
Artikel Terkait
- API Development Best Practices — Membangun API yang Secure & Scalable
- Web Performance Optimization — Percepat Aplikasi Kamu
- Database Design Fundamentals — Membangun Database yang Efisien
- REST API Architecture — Memahami Modern Web API
Punya pengalaman mengimplementasikan ETag di API Kamu? Bagikan insights dan lessons learned Kamu di kolom komentar! 💬