Axios Interceptor — token qo'shish, xatolarni markaziy ushlash
Muammodan boshlaylik
Ilovada 40 ta API so'rov bor. Har biriga:
const token = localStorage.getItem('token');
const j = await axios.get('https://api.dokon.uz/v1/buyurtmalar', {
headers: { Authorization: `Bearer ${token}` },
});
Va har birida:
} catch (err) {
if (err.response?.status === 401) {
localStorage.removeItem('token');
window.location.href = '/login';
}
if (err.response?.status === 500) toast.error('Server xatosi');
}
40 marta takrorlangan kod. Bir kun backend token formatini o'zgartiradi — 40 ta faylni tahrirlash kerak. Bitta joyda unutsangiz — xato.
Interceptor (ushlab qoluvchi) — barcha so'rovlar va javoblar o'tadigan markaziy nuqta. Umumiy mantiqni bir marta yozasiz.
Axios instance
Avval — loyiha uchun alohida Axios nusxasi:
// src/api/client.js
import axios from 'axios';
export const api = axios.create({
baseURL: import.meta.env.VITE_API_URL, // https://api.dokon.uz/v1
timeout: 10_000,
headers: { 'Content-Type': 'application/json' },
});
// Endi har joyda
const { data } = await api.get('/buyurtmalar');
baseURL— har so'rovda to'liq manzil yozish shart emastimeout— 10 soniyada javob bo'lmasa — xato (standart holatda cheksiz kutadi)- Global
axiosga tegmaymiz — boshqa API lar (masalan, uchinchi tomon xizmati) uchun alohida instance bo'lishi mumkin
Request interceptor — har so'rovdan oldin
import { useAuthStore } from '@/store/authStore';
api.interceptors.request.use((config) => {
const token = useAuthStore.getState().token; // Zustand — React dan tashqarida (57-dars)
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
config.headers['Accept-Language'] = i18n.language; // 86-dars
return config; // ← majburiy
});
Funksiya so'rov sozlamalarini (config) oladi, o'zgartiradi va qaytaradi. return config ni unutsangiz — so'rov ketmaydi.
Endi 40 ta so'rovning hech birida token haqida o'ylash shart emas.
Response interceptor — har javobdan keyin
api.interceptors.response.use(
(javob) => javob, // 2xx — muvaffaqiyat: o'zgarishsiz
(xato) => { // 4xx, 5xx, tarmoq xatosi
const status = xato.response?.status;
if (!xato.response) {
toast.error("Internet aloqasi yo'q");
} else if (status === 403) {
toast.error("Bu amal uchun ruxsatingiz yo'q");
} else if (status >= 500) {
toast.error("Serverda xatolik. Keyinroq urinib ko'ring");
}
return Promise.reject(xato); // ← xatoni chaqiruvchiga uzatish
}
);
- Birinchi funksiya — muvaffaqiyatli javob uchun
- Ikkinchi — xato uchun
Promise.reject(xato)— xatoni davom ettirish: TanStack Query,try/catchyoki Error Boundary uni baribir oladi
[!WARNING] Xato interceptor ida
return Promise.reject(xato)ni unutsangiz, xato yutib yuboriladi: chaqiruvchi kodundefinedni "muvaffaqiyatli javob" deb oladi. TanStack QueryisSuccess: trueko'rsatadi,dataesaundefined— topish qiyin xato.
Ma'lumotni "ochish"
Ko'p API lar javobni o'raydi: { success: true, data: {...} }. Interceptor bilan:
api.interceptors.response.use((javob) => javob.data.data);
// Endi: const buyurtmalar = await api.get('/buyurtmalar'); — to'g'ridan-to'g'ri ma'lumot
Lekin ehtiyot bo'ling: bu barcha so'rovlarga ta'sir qiladi va header lar, status kabi ma'lumotlarga yetib bo'lmay qoladi. Ko'pincha API funksiyalarida (return data) ochish aniqroq (89-dars).
401 — token muddati tugadi
Eng muhim stsenariy. Odatda ikki token bo'ladi:
- Access token — qisqa muddatli (15 daqiqa), har so'rovda yuboriladi
- Refresh token — uzoq muddatli (30 kun), faqat yangi access token olish uchun
Access token muddati tugadi → server 401 qaytaradi. Foydalanuvchini login sahifasiga haydash o'rniga — jimgina yangi token olib, so'rovni qaytadan yuborish:
let yangilanmoqda = null; // bir vaqtda faqat bitta refresh so'rovi
api.interceptors.response.use(
(javob) => javob,
async (xato) => {
const asl = xato.config;
if (xato.response?.status === 401 && !asl._qaytaUrinish && !asl.url.includes('/auth/refresh')) {
asl._qaytaUrinish = true; // cheksiz siklni oldini olish
try {
yangilanmoqda ??= tokenniYangila(); // hamma bitta Promise ni kutadi
const yangiToken = await yangilanmoqda;
asl.headers.Authorization = `Bearer ${yangiToken}`;
return api(asl); // so'rovni qaytadan yuborish
} catch {
useAuthStore.getState().chiqish();
window.location.assign('/login');
return Promise.reject(xato);
} finally {
yangilanmoqda = null;
}
}
return Promise.reject(xato);
}
);
async function tokenniYangila() {
const { data } = await axios.post( // instance emas — interceptor siz
`${import.meta.env.VITE_API_URL}/auth/refresh`,
{},
{ withCredentials: true } // refresh token — httpOnly cookie da
);
useAuthStore.getState().setToken(data.accessToken);
return data.accessToken;
}
Muhim nuqtalar:
| Nuqta | Nima uchun |
|---|---|
_qaytaUrinish bayrog'i |
Yangi token bilan ham 401 bo'lsa — cheksiz siklga tushmaslik |
/auth/refresh ni istisno qilish |
Refresh ning o'zi 401 bersa — qayta refresh qilmaslik |
Umumiy yangilanmoqda Promise |
Sahifa 5 ta so'rov yubordi, hammasi 401 oldi → bitta refresh so'rovi, beshalasi uni kutadi |
Refresh — oddiy axios bilan |
Instance interceptor lari refresh so'roviga aralashmasin |
| Muvaffaqiyatsiz bo'lsa — chiqish | Refresh token ham eskirgan — qayta kirish kerak |
Foydalanuvchi hech narsani sezmaydi — so'rov biroz kechikib, lekin muvaffaqiyatli bajariladi.
Interceptor ni olib tashlash
const id = api.interceptors.request.use(fn);
api.interceptors.request.eject(id);
React komponentida interceptor qo'shilsa (masalan, Context dagi token bilan), effekt cleanup ida olib tashlash shart — aks holda har renderda yangi interceptor qo'shiladi (84-dars):
useEffect(() => {
const id = api.interceptors.request.use((config) => { ... });
return () => api.interceptors.request.eject(id);
}, [token]);
Lekin yaxshiroq — interceptor larni modul darajasida bir marta yozish va token ni store dan getState() bilan olish (yuqoridagidek).
fetch bilan
Axios siz ham xuddi shu g'oyani o'z funksiyangiz bilan amalga oshirish mumkin:
// src/api/client.js
export async function apiFetch(yol, options = {}) {
const token = useAuthStore.getState().token;
const j = await fetch(`${import.meta.env.VITE_API_URL}${yol}`, {
...options,
headers: {
'Content-Type': 'application/json',
...(token && { Authorization: `Bearer ${token}` }),
...options.headers,
},
signal: options.signal ?? AbortSignal.timeout(10_000),
});
if (j.status === 401) { /* refresh mantiqi */ }
if (!j.ok) throw new ApiXato(j.status, await j.json().catch(() => null));
return j.status === 204 ? null : j.json();
}
"Interceptor" — bu shunchaki barcha so'rovlar o'tadigan bitta funksiya. Axios uni tayyor holda beradi.
[!TIP] Token ni qayerda saqlash — muhim xavfsizlik savoli.
localStoragedagi token sahifadagi har qanday JavaScript (XSS hujumi) tomonidan o'qilishi mumkin. Keng tarqalgan xavfsizroq yondashuv: access token — xotirada (Zustand, persist siz), refresh token — server o'rnatganhttpOnlycookie da (JavaScript uni umuman ko'rmaydi). Sahifa yangilanganda ilova refresh orqali yangi access token oladi.
Nima uchun shunday?
Nima uchun autentifikatsiya va xatolarni qayta ishlashni markazlashtirish shunchalik muhim?
Chunki bu — "cross-cutting concern": ilovaning har bir qismiga tegishli, lekin hech qaysi birining asosiy vazifasi emas. Buyurtmalar sahifasi buyurtmalar haqida o'ylashi kerak, token formati yoki refresh mantiqi haqida emas. Bunday mantiq har joyda takrorlansa, uch muammo paydo bo'ladi: o'zgartirish qimmat (40 ta fayl), izchillik yo'q (bir joyda 403 boshqacha ko'rsatiladi) va xavfsizlik zaif (bir joyda token qo'shishni unutish). Interceptor — "middleware" g'oyasining frontend versiyasi: Express dagi middleware lar kabi, u har bir so'rov va javobni o'z qo'lidan o'tkazadi va umumiy qoidalarni bir joyda qo'llaydi.
Xulosa
Eslab qoling:
axios.create({ baseURL, timeout })— loyiha uchun alohida instanceinterceptors.request.use(config => ...; return config)— token, til, umumiy header larinterceptors.response.use(ok, xato => ...; return Promise.reject(xato))— markaziy xatolarPromise.rejectni unutmang — aks holda xato yutiladi- 401 + refresh token:
_qaytaUrinishbayrog'i, refresh ni istisno qilish, umumiy Promise, so'rovni qayta yuborish - Interceptor lar — modul darajasida; komponentda bo'lsa
ejectbilan cleanup - Token: access — xotirada, refresh —
httpOnlycookie da
Keyingi dars
86-dars: i18n — react-i18next bilan ko'p tilli ilova