Content negotiation adalah mekanisme HTTP yang memungkinkan satu URL menyajikan representasi berbeda dari resource yang sama — tergantung kesukaan client. Endpoint /users/42 bisa mengembalikan JSON untuk aplikasi mobile, HTML untuk browser, atau XML untuk partner legacy — semuanya dari URL yang sama.
Mengapa perlu content negotiation? Bayangkan API yang dipakai banyak client berbeda: browser ingin HTML, aplikasi mobile ingin JSON, third-party service ingin XML. Daripada membuat tiga URL terpisah, kita biarkan client mengatakan "saya mau format X" lewat header — server pilih yang paling cocok.
Header request yang digunakan client:
| Header | Fungsi | Contoh |
|---|---|---|
Accept |
Format konten yang diinginkan | application/json, text/html;q=0.9 |
Accept-Language |
Bahasa yang diinginkan | id-ID, en-US;q=0.8 |
Accept-Encoding |
Kompresi yang didukung | gzip, br, deflate |
Accept-Charset |
Character set (jarang dipakai sekarang) | utf-8 |
Quality values (q-factor):
Client bisa memberi bobot preferensi dengan q=. Nilai antara 0 dan 1 — semakin tinggi, semakin disukai. Default q=1.
Accept: application/json;q=1.0, application/xml;q=0.8, text/html;q=0.5
Artinya: "saya paling suka JSON, XML masih oke, HTML sebagai pilihan terakhir." Server memilih format dengan q tertinggi yang bisa ia hasilkan.
Contoh negosiasi bahasa:
Accept-Language: id-ID, id;q=0.9, en-US;q=0.7, en;q=0.5
Client bilang: "Prioritas bahasa Indonesia Indonesia, lalu Indonesia umum, kalau tidak ada baru English US, terakhir English apapun."
Server membalas dengan Content-Type:
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Content-Language: id-ID
{"id": 42, "name": "Budi"}
Server memberi tahu: "Ini yang saya pilihkan untukmu."
Ketika tidak ada yang cocok: 406 Not Acceptable
Kalau client minta format yang tidak bisa dihasilkan server, dan server ingin strict, ia boleh membalas 406:
GET /users/42
Accept: application/x-yaml
HTTP/1.1 406 Not Acceptable
Content-Type: application/json
{"error": "Only JSON and HTML are supported"}
Dalam praktik, banyak API memilih fallback ke default daripada 406, untuk pengalaman yang lebih ramah.
Vary header — penting untuk cache:
Kalau satu URL bisa mengembalikan banyak representasi, cache (CDN, proxy) harus tahu: "response ini tergantung header apa?" Itulah gunanya Vary.
HTTP/1.1 200 OK
Content-Type: application/json
Vary: Accept, Accept-Language
Artinya: "Cache response ini terpisah untuk kombinasi Accept dan Accept-Language yang berbeda." Tanpa Vary, CDN bisa menyajikan JSON ke user yang sebenarnya minta HTML — bug yang sulit dilacak.
Implementasi di Laravel:
Route::get("/users/{id}", function ($id, Request $request) {
$user = User::findOrFail($id);
if ($request->wantsJson()) {
return response()->json($user);
}
if ($request->accepts("text/html")) {
return view("users.show", ["user" => $user]);
}
abort(406);
});
Implementasi di Node.js (Express):
app.get("/users/:id", (req, res) => {
const user = getUser(req.params.id);
res.format({
"application/json": () => res.json(user),
"text/html": () => res.render("user", { user }),
default: () => res.status(406).send("Not Acceptable"),
});
});
Parsing Accept di tangan — contoh JavaScript:
function parseAccept(header) {
return header.split(",").map((item) => {
const [type, ...params] = item.trim().split(";");
const q = params.find((p) => p.trim().startsWith("q="));
return {
type: type.trim(),
quality: q ? parseFloat(q.split("=")[1]) : 1.0,
};
}).sort((a, b) => b.quality - a.quality);
}
parseAccept("text/html, application/json;q=0.9, */*;q=0.1");
// [{type:"text/html", quality:1}, {type:"application/json", quality:0.9}, ...]
Wildcard di Accept:
*/*— format apa sajatext/*— text apapun (html, plain, xml)application/*— aplikasi apapun (json, xml, pdf)
Biasanya browser mengirim sesuatu seperti text/html, */*;q=0.8 — berarti "saya prioritas HTML, tapi apapun oke."
Tips praktis content negotiation:
- Default ke format paling umum (JSON untuk API modern) kalau client tidak spesifik
- Selalu set
Varyjika response bervariasi berdasarkan header - Jangan paksa 406 — pengalaman user lebih penting daripada strictness
- Untuk API versioning,
Accept: application/vnd.myapp.v2+jsonlebih elegan daripada URL/v2/ - Log
Acceptheader untuk memahami klien kamu
Content negotiation adalah contoh elegan bagaimana HTTP memungkinkan satu resource tampil berbeda untuk kebutuhan berbeda — tanpa duplikasi endpoint.