ETag dan Conditional Request: Kuasai HTTP Caching untuk API

ETag dan Conditional Request: Kuasai HTTP Caching untuk API

3/9/2026 API Development By Tech Writers
ETagHTTP HeadersAPI CachingConditional RequestPerformanceWeb APIBackend

Daftar Isi

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


Punya pengalaman mengimplementasikan ETag di API Kamu? Bagikan insights dan lessons learned Kamu di kolom komentar! 💬