인터셉터로 요청/응답 제어하기 📤 요청 인터셉터 axios.interceptors .request .use ( (config ) => { console .log (`🚀 API 요청: ${config.method?.toUpperCase()} ${config.url} ` ); const token = localStorage .getItem ('accessToken' ); if (token) { config.headers .Authorization = `Bearer ${token} ` ; } config.metadata = { startTime : new Date () }; return config; }, (error ) => { console .error ('❌ 요청 설정 오류:' , error); return Promise .reject (error); } );
📥 응답 인터셉터 axios.interceptors .response .use ( (response ) => { const endTime = new Date (); const duration = endTime - response.config .metadata .startTime ; console .log (`✅ API 응답: ${response.config.url} (${duration} ms)` ); return response; }, async (error) => { const originalRequest = error.config ; if (error.response ?.status === 401 && !originalRequest._retry ) { originalRequest._retry = true ; try { const refreshToken = localStorage .getItem ('refreshToken' ); const response = await [axios.post ](http : refreshToken }); const newToken = [response.data ](http : localStorage .setItem ('accessToken' , newToken); originalRequest.headers .Authorization = `Bearer ${newToken} ` ; return axios (originalRequest); } catch (refreshError) { localStorage .clear (); window .location .href = '/login' ; } } console .error ('❌ API 에러:' , { url : error.config ?.url , status : error.response ?.status , message : error.message }); return Promise .reject (error); } );
실전 예제: API 클래스 구현 🏢 사용자 관리 API 클래스 class UserService { constructor ( ) { this .client = axios.create ({ baseURL : process.env .REACT_APP_API_URL || 'https://api.example.com' , timeout : 10000 }); this .setupInterceptors (); } setupInterceptors ( ) { this .client .interceptors .request .use (config => { const token = this .getAuthToken (); if (token) { config.headers .Authorization = `Bearer ${token} ` ; } return config; }); this .client .interceptors .response .use ( response => response, error => this .handleError (error) ); } getAuthToken ( ) { return localStorage .getItem ('authToken' ); } handleError (error ) { const message = error.response ?.data ?.message || error.message ; throw new Error (`API 요청 실패: ${message} ` ); } async getUsers (options = {} ) { const { page = 1 , limit = 10 , search = '' } = options; const response = await this .client .get ('/users' , { params : { page, limit, search } }); return { users : [response.data .data ](http : totalCount : [response.data .total ](http : currentPage : page }; } async getUserById (userId ) { const response = await this .client .get (`/users/${userId} ` ); return [response.data ](http : } async createUser (userData ) { const response = await [this .client .post ](http : return [response.data ](http : } async updateUser (userId, userData ) { const response = await this .client .put (`/users/${userId} ` , userData); return [response.data ](http : } async deleteUser (userId ) { await this .client .delete (`/users/${userId} ` ); return { success : true , userId }; } async searchUsers (query ) { const response = await this .client .get ('/users/search' , { params : { q : query } }); return [response.data ](http : } } export const userService = new UserService ();async function handleUserOperations ( ) { try { const userList = await userService.getUsers ({ page : 1 , limit : 20 , search : 'john' }); console .log ('사용자 목록:' , userList); const newUser = await userService.createUser ({ name : '김개발' , email : '[kim.dev@example.com](mailto:kim.dev@example.com)' , role : 'developer' }); console .log ('생성된 사용자:' , newUser); const updatedUser = await userService.updateUser ([newUser.id ](http : name : '김시니어' , role : 'senior-developer' }); console .log ('수정된 사용자:' , updatedUser); } catch (error) { console .error ('사용자 작업 중 오류:' , error.message ); } }
파일 업로드 및 다운로드 📁 파일 업로드 async function uploadFile (file, onProgress ) { const formData = new FormData (); formData.append ('file' , file); formData.append ('category' , 'documents' ); formData.append ('description' , [file.name ](http : try { const response = await [axios.post ](http : headers : { 'Content-Type' : 'multipart/form-data' }, onUploadProgress : (progressEvent ) => { const percentCompleted = Math .round ( (progressEvent.loaded * 100 ) / [progressEvent.total ](http : ); console .log (`업로드 진행률: ${percentCompleted} %` ); onProgress?.(percentCompleted); } }); return [response.data ](http : } catch (error) { console .error ('파일 업로드 실패:' , error); throw error; } } async function uploadMultipleFiles (files ) { const formData = new FormData (); files.forEach ((file, index ) => { formData.append (`files` , file); }); const response = await [axios.post ](http : headers : { 'Content-Type' : 'multipart/form-data' } }); return [response.data ](http : } const fileInput = document .getElementById ('fileInput' );fileInput.addEventListener ('change' , async (event) => { const file = [event.target ](http : if (file) { try { const result = await uploadFile (file, (progress ) => { console .log (`업로드 진행률: ${progress} %` ); }); console .log ('업로드 완료:' , result); } catch (error) { console .error ('업로드 에러:' , error); } } });
📥 파일 다운로드 async function downloadFile (fileId, filename ) { try { const response = await axios.get (`/api/files/${fileId} /download` , { responseType : 'blob' }); const url = window .URL .createObjectURL (new Blob ([[response.data ](http : const link = document .createElement ('a' ); link.href = url; link.setAttribute ('download' , filename); document .body .appendChild (link); [link.click ](http : document .body .removeChild (link); window .URL .revokeObjectURL (url); } catch (error) { console .error ('파일 다운로드 실패:' , error); } }
에러 처리 베스트 프랙티스 🛡️ 포괄적인 에러 처리 function handleApiError (error ) { if (error.response ) { const { status, data } = error.response ; switch (status) { case 400 : console .error ('잘못된 요청:' , data.message ); break ; case 401 : console .error ('인증 실패 - 로그인이 필요합니다' ); window .location .href = '/login' ; break ; case 403 : console .error ('접근 권한이 없습니다' ); break ; case 404 : console .error ('요청한 리소스를 찾을 수 없습니다' ); break ; case 429 : console .error ('요청 한도를 초과했습니다. 잠시 후 다시 시도해주세요' ); break ; case 500 : console .error ('서버 내부 오류가 발생했습니다' ); break ; default : console .error (`서버 오류 (${status} ):` , data.message ); } return { type : 'response' , status, message : data.message }; } else if (error.request ) { console .error ('네트워크 오류: 서버에 연결할 수 없습니다' ); return { type : 'network' , message : '네트워크 연결을 확인해주세요' }; } else { console .error ('요청 설정 오류:' , error.message ); return { type : 'config' , message : error.message }; } } async function apiCallWithRetry (requestFn, maxRetries = 3 , delay = 1000 ) { for (let attempt = 1 ; attempt <= maxRetries; attempt++) { try { return await requestFn (); } catch (error) { const errorInfo = handleApiError (error); if (attempt === maxRetries || errorInfo.type === 'response' ) { throw error; } console .log (`재시도 ${attempt} /${maxRetries} - ${delay} ms 후 다시 시도...` ); await new Promise (resolve => setTimeout (resolve, delay * attempt)); } } } async function fetchUserData (userId ) { return apiCallWithRetry ( () => axios.get (`/api/users/${userId} ` ), 3 , 1000 ); }
성능 최적화 팁 ⚡ 요청 최적화 기법 class ApiService { constructor ( ) { this .pendingRequests = new Map (); } async fetchData (endpoint, options = {} ) { if (this .pendingRequests .has (endpoint)) { this .pendingRequests .get (endpoint).abort (); } const controller = new AbortController (); this .pendingRequests .set (endpoint, controller); try { const response = await axios.get (endpoint, { ...options, signal : controller.signal }); this .pendingRequests .delete (endpoint); return [response.data ](http : } catch (error) { this .pendingRequests .delete (endpoint); if (axios.isCancel (error)) { console .log ('요청이 취소되었습니다:' , endpoint); } else { throw error; } } } cancelAllRequests ( ) { this .pendingRequests .forEach (controller => controller.abort ()); this .pendingRequests .clear (); } } function debounce (func, wait ) { let timeout; return function executedFunction (...args ) { const later = ( ) => { clearTimeout (timeout); func (...args); }; clearTimeout (timeout); timeout = setTimeout (later, wait); }; } const debouncedSearch = debounce (async (query) => { if (query.trim ()) { const results = await axios.get ('/api/search' , { params : { q : query } }); console .log ('검색 결과:' , [results.data ](http : } }, 300 ); class RateLimitedApi { constructor (maxConcurrent = 5 ) { this .maxConcurrent = maxConcurrent; this .running = 0 ; this .queue = []; } async request (config ) { return new Promise ((resolve, reject ) => { this .queue .push ({ config, resolve, reject }); this .processQueue (); }); } async processQueue ( ) { if (this .running >= this .maxConcurrent || this .queue .length === 0 ) { return ; } this .running ++; const { config, resolve, reject } = this .queue .shift (); try { const response = await axios (config); resolve (response); } catch (error) { reject (error); } finally { this .running --; this .processQueue (); } } } const rateLimitedApi = new RateLimitedApi (3 );
🎯 마무리 Axios는 JavaScript 생태계에서 가장 강력하고 유연한 HTTP 클라이언트 라이브러리입니다. 이 가이드에서 다룬 내용들을 실제 프로젝트에 적용하면 더욱 안정적이고 효율적인 API 통신을 구현할 수 있습니다.
핵심 포인트 정리 ✅ 인스턴스 활용 : 공통 설정을 가진 인스턴스로 코드 중복 제거 ✅ 인터셉터 사용 : 인증, 로깅, 에러처리 등 공통 로직 처리 ✅ 에러 처리 : 다양한 에러 상황에 대한 체계적인 대응 ✅ 성능 최적화 : 요청 취소, 디바운싱, 동시 요청 제한 ✅ 타입 안정성 : TypeScript와 함께 사용하여 더욱 안전한 코드 작성
Axios를 마스터하여 더 나은 웹 애플리케이션을 만들어보세요! 🚀