Episode ini membahas keamanan data: menangani respons 401 dan 403 dengan refresh token, menghindari penyimpanan data sensitif di cache, sanitasi error, serta best practice query key dan pemisahan server state dengan client state.

Cache yang menyimpan data server adalah pedang bermata dua: di satu sisi mempercepat aplikasi, di sisi lain menyimpan data yang bisa bocor jika tidak dikelola dengan hati-hati. Memikirkan keamanan setelah aplikasi besar bukanlah pilihan — dasar-dasarnya harus tertanam sejak awal.
Episode 15 membahas keamanan dari sudut TanStack Query: cara menangani 401 dan 403, aturan data sensitif di cache, sanitasi error, lalu best practice struktur kode yang menjaga project tetap sehat.
Ketika token akses kedaluwarsa, server mengembalikan 401. Pola umum: refresh token otomatis, lalu ulangi permintaan. TanStack Query tidak menyediakan interceptor seperti axios, jadi handle di queryFn atau di lapisan API:
async function authedFetch(url) {
const res = await fetch(url, {
headers: { Authorization: `Bearer ${getAccessToken()}` },
})
if (res.status === 401) {
const refreshed = await tryRefreshToken()
if (refreshed) {
return authedFetch(url)
}
redirectToLogin()
throw new Error("Sesi berakhir")
}
if (res.status === 403) {
throw new Error("Akses ditolak")
}
return res.json()
}Saat res.status === 401, fungsi mencoba refresh token lalu mengulang request sekali. Jika refresh gagal, user diarahkan ke login. authedFetch mengkonsolidasikan logika autentikasi sehingga semua queryFn memakai satu jalur yang aman.
Daripada menulis ulang logika 401 di setiap queryFn, jadikan authedFetch pembungkus standar semua panggilan API:
const { data } = useQuery({
queryKey: ["todos"],
queryFn: () => authedFetch("/todos"),
})authedFetch("/todos") memberi semua query penanganan 401 yang konsisten. Kesalahan umum: menangani 401 di beberapa tempat dengan perilaku yang berbeda, sehingga sesi kedaluwarsa menampilkan error acak di tiap halaman.
403 berarti pengguna valid tapi tidak berhak. Tangani berbeda dari 401 — tidak ada gunanya refresh token. Tampilkan pesan "akses ditolak" dan jangan meng-cache respons 403.
Cache TanStack Query hidup di memori, dan jika dipersist (episode 12) akan tersimpan di localStorage yang bisa dibaca skrip di origin yang sama. Aturan sederhana:
gcTime yang pendek untuk data sensitif.Objek error yang dilempar queryFn bisa saja berisi detail internal yang tidak layak ditampilkan. Jangan pernah menampilkan error.message dari server langsung ke UI:
function getUserFacingMessage(error) {
if (error instanceof AuthError) return "Sesi berakhir, silakan masuk lagi"
if (error instanceof ApiError) return "Terjadi kesalahan, coba lagi nanti"
return "Kesalahan tak terduga"
}getUserFacingMessage memetakan tipe error ke pesan yang aman untuk user. Jangan menampilkan stack trace atau detail response mentah ke UI — simpan detail teknis di log yang hanya diakses developer.
Dari episode 5, konsistensi query key adalah kunci. Tetapkan konvensi di awal:
["todos", "list", { filter }]
["todos", "detail", id]
["users", id, "posts"]Konvensi ["todos", "list", { filter }] menetapkan pola domain → resource → modifikasi. Konvensi yang konsisten membuat invalidasi mudah dinalar dan mencegah key duplikat yang diam-diam membaca cache salah.
Ini prinsip paling penting dari seluruh series: TanStack Query untuk server state, bukan client state. UI state seperti modal terbuka, tab aktif, theme, atau isi draft form adalah client state — simpan di useState, Zustand, atau Redux. Mencampur keduanya membuat cache menampung data yang sebenarnya bukan tanggung jawabnya.
TanStack Query → data dari server (todos, users, profil)
Zustand/Redux → UI state (modal, theme, keranjang, filter sementara)Pembagian TanStack Query → data dari server dan Zustand/Redux → UI state menghindari duplikasi dan kebingungan. Episode 16 akan membahas pola hybrid ini lebih dalam.
Tip
Mulai project dengan menuliskan konvensi query key dan aturan data sensitif di dokumentasi tim. Keputusan kecil ini menyelamatkan banyak bug keamanan dan inkonsistensi di masa depan.
Episode 15 membekali kalian praktik keamanan dan struktur: penanganan 401 dan 403 yang konsisten, aturan data sensitif di cache, sanitasi error untuk user, serta best practice query key dan pemisahan server state dari client state.
Inti yang harus dibawa pulang:
Di episode 16 selanjutnya kita akan membahas integrasi dengan state management — pola hybrid TanStack Query untuk server state dengan Zustand atau Redux untuk UI state, serta memperlakukan cache sebagai single source of truth dengan getQueryData.