Route Handlers Lanjutan
Query params, headers, cookies, dan streaming response.
Contoh 1: membaca seluruh isi request
Route Handler yang serius jarang cuma me-return data statis. Ia perlu membaca query string, header otorisasi, dan cookie:
// app/api/cari/route.ts
import { cookies, headers } from "next/headers";
export async function GET(req: Request) {
const { searchParams } = new URL(req.url);
const q = searchParams.get("q") ?? "";
const halaman = Number(searchParams.get("halaman") ?? "1");
if (!q) {
return Response.json({ error: "Parameter q wajib diisi" }, { status: 400 });
}
const token = (await headers()).get("authorization");
const theme = (await cookies()).get("theme")?.value ?? "light";
const hasil = await db.artikel.search(q, halaman);
return Response.json({ q, halaman, punyaToken: !!token, theme, hasil });
}Tiga sumber data, tiga cara baca: searchParams untuk ?q=..., headers() untuk token otorisasi, cookies() untuk preferensi user. Di Next.js 15+, headers() dan cookies() adalah fungsi async, jangan lupa await atau kamu akan memegang Promise, bukan nilainya.
Pola validasi di awal (early return 400 bila q kosong) membuat sisa fungsi bersih: setelah lolos validasi, kamu tahu semua input sudah aman dipakai.
Contoh 2: response kustom dan streaming
Tidak semua response adalah JSON. Untuk file teks, CSV, atau unduhan, pakai Response biasa:
// app/api/laporan/route.ts
export async function GET() {
const csv = "nama,skor\nBudi,90\nSiti,85";
return new Response(csv, {
status: 200,
headers: {
"Content-Type": "text/csv",
"Content-Disposition": "attachment; filename=laporan.csv",
},
});
}Dan untuk response yang mengalir sedikit demi sedikit (misalnya output AI yang diketik per kata), gunakan streaming:
// app/api/stream/route.ts
export async function GET() {
const stream = new ReadableStream({
start(controller) {
const kata = ["Halo", "ini", "streaming", "response"];
let i = 0;
const timer = setInterval(() => {
if (i >= kata.length) {
clearInterval(timer);
controller.close();
return;
}
controller.enqueue(new TextEncoder().encode(kata[i++] + " "));
}, 300);
},
});
return new Response(stream, { headers: { "Content-Type": "text/plain" } });
}Client menerima potongan data begitu tersedia tanpa menunggu response selesai total. Ini fondasi fitur seperti chatbot yang mengetik jawabannya secara live.
Tips: error handling yang rapi
Endpoint yang baik tidak pernah melempar error mentah ke client. Bungkus logika yang berisiko (query database, fetch eksternal) dengan try/catch dan kembalikan status yang jujur:
// app/api/artikel/route.ts
import { NextResponse } from "next/server";
export async function GET() {
try {
const data = await db.artikel.findMany();
return NextResponse.json(data);
} catch (e) {
console.error(e);
return NextResponse.json({ error: "Gagal mengambil data" }, { status: 500 });
}
}Client bisa membedakan: 400 berarti request-nya salah, 401/403 berarti soal izin, 404 berarti data tidak ada, 500 berarti server yang bermasalah. Konsistensi ini membuat frontend jauh lebih mudah menangani error.
Kapan Route Handler, kapan Server Action?
| Route Handler | Server Action | |
|---|---|---|
| Dipanggil dari | Mana saja, termasuk eksternal | Form atau komponen sendiri |
| Bentuk | HTTP REST (GET/POST/...) | Fungsi async biasa |
| Webhook pihak ketiga | Cocok | Tidak bisa |
| Butuh URL publik | Ya | Tidak |
Aturan praktisnya: kalau pemanggilnya adalah form di aplikasimu sendiri, Server Action lebih sederhana. Kalau pemanggilnya bisa siapa saja (aplikasi mobile, webhook Stripe, cron eksternal), Route Handler adalah jawabannya.
Tantangan
API berkas
Buat GET /api/cuaca?kota=X yang me-return JSON cuaca dummy berdasarkan query kota. Bila kota kosong, return 400 dengan pesan error.