~/zafer
Tüm yazılara dön
2 dk okuma

Admin Panelinde 401'de Otomatik Çıkış Yapan Bir Fetch Wrapper

Admin panelindeki her API isteği (useApiQuery/useApiMutation'ın fetcher/mutationFn closure'ları içinde) aynı api() fonksiyonundan geçiyor:

// apps/admin/src/lib/api.ts
export class ApiError extends Error {
  status: number;
  constructor(status: number, message: string) {
    super(message);
    this.status = status;
  }
}

export async function api<T>(path: string, init?: RequestInit): Promise<T> {
  const token = useAuth.getState().token;
  const res = await fetch(`/api${path}`, {
    ...init,
    headers: { "Content-Type": "application/json", ...(token ? { Authorization: `Bearer ${token}` } : {}), ...init?.headers },
  });

  if (res.status === 401) useAuth.getState().logout();
  if (!res.ok) throw new ApiError(res.status, (await res.text()) || res.statusText);
  if (res.status === 204) return undefined as T;
  return res.json();
}

useAuth.getState() — hook olmayan bir yerden state okumak

api() bir React hook değil, düz bir async fonksiyon — bileşen ağacının dışında da çağrılabiliyor. Zustand'ın getState() metodu, useAuth() hook'unu çağırmadan store'un o anki değerine erişmeyi sağlıyor. Bu sayede token'ı her api() çağıran bileşenin prop olarak geçmesine gerek kalmıyor — merkezi store'dan doğrudan okunuyor.

401 nerede yakalanıyor, nerede yakalanmıyor

api() 401 aldığında logout()'u çağırıyor ama sonra yine de !res.ok kontrolüne düşüp ApiError fırlatıyor — yani "oturumu temizle" ile "bu isteğin başarısız olduğunu çağırana bildir" iki ayrı, birbirini engellemeyen sorumluluk. Çağıran taraf (useApiMutation içindeki catch) hatayı normal şekilde yakalarken, kullanıcı arka planda zaten login sayfasına yönlendirilmiş oluyor — bu iki davranış tek bir noktada, her api() çağrısı için otomatik.

Neden özel bir ApiError sınıfı

fetch'in kendisi HTTP hata kodlarında (404, 401, 500) exception fırlatmıyor — sadece ağ hatasında fırlatıyor. ApiError, HTTP durumunu (status) hata nesnesinin bir parçası yaparak, catch bloklarının "bu 404 mü 500 mü" ayrımını error instanceof ApiError + error.status ile yapabilmesini sağlıyor; generic Error'da bu bilgi kaybolurdu.