[Javascript] Axios 사용하기 (고급편)

고급 설정 및 인스턴스

⚙️ 상세한 요청 설정

const requestConfig = {
method: 'get',
url: '/api/users',
baseURL: 'https://api.example.com',

// 헤더 설정
headers: {
'Authorization': 'Bearer token123',
'Content-Type': 'application/json',
'Accept': 'application/json'
},

// URL 파라미터
params: {
page: 1,
size: 20
},

// 타임아웃 (밀리초)
timeout: 10000,

// 응답 타입
responseType: 'json', // 'json', 'text', 'blob', 'stream' 등

// 요청 바디 (POST, PUT, PATCH)
data: {
name: '홍길동',
email: '[hong@example.com](mailto:hong@example.com)'
}
};

const response = await axios(requestConfig);

🏗️ Axios 인스턴스 생성

// API별 인스턴스 생성
const apiClient = axios.create({
baseURL: 'https://api.myservice.com/v1',
timeout: 15000,
headers: {
'Content-Type': 'application/json'
}
});

// 인증이 필요한 API용 인스턴스
const authApiClient = axios.create({
baseURL: 'https://api.myservice.com/v1',
timeout: 10000,
headers: {
'Authorization': `Bearer ${getAuthToken()}`
}
});

// 인스턴스 사용
const users = await apiClient.get('/users');
const profile = await authApiClient.get('/profile');

인터셉터로 요청/응답 제어하기

웹 개발을 하다 보면 서버와 통신할 때 반복적으로 처리해야 하는 공통 로직들이 생깁니다.
예를 들어 JWT 토큰을 모든 요청 헤더에 붙인다거나, 에러 응답을 일관되게 처리한다거나, 응답 데이터 구조를 통일한다거나 하는 일들이죠.

이때 유용하게 사용할 수 있는 기능이 바로 Axios Interceptor입니다.

📤 요청 인터셉터

// 모든 요청에 공통 로직 적용
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;

// 401 에러: 토큰 갱신 처리
if (error.response?.status === 401 && !originalRequest._retry) {
originalRequest._retry = true;

try {
const refreshToken = localStorage.getItem('refreshToken');
const response = await [axios.post](http://axios.post)('/auth/refresh', {
refreshToken
});

const newToken = [response.data](http://response.data).accessToken;
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://response.data.data),
totalCount: [response.data.total](http://response.data.total),
currentPage: page
};
}

// 사용자 상세 조회
async getUserById(userId) {
const response = await this.client.get(`/users/${userId}`);
return [response.data](http://response.data);
}

// 사용자 생성
async createUser(userData) {
const response = await [this.client.post](http://this.client.post)('/users', userData);
return [response.data](http://response.data);
}

// 사용자 정보 수정
async updateUser(userId, userData) {
const response = await this.client.put(`/users/${userId}`, userData);
return [response.data](http://response.data);
}

// 사용자 삭제
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://response.data);
}
}

// 싱글톤 패턴으로 사용
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://newUser.id), {
name: '김시니어',
role: 'senior-developer'
});
console.log('수정된 사용자:', updatedUser);

} catch (error) {
console.error('사용자 작업 중 오류:', error.message);
}
}

성능 최적화 팁

⚡ 요청 최적화 기법

// 1. 요청 취소 (AbortController 사용)
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://response.data);

} 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();
}
}

// 2. 요청 디바운싱 (검색 등에 유용)
function debounce(func, wait) {
let timeout;
return function executedFunction(...args) {
const later = () => {
clearTimeout(timeout);
func(...args);
};
clearTimeout(timeout);
timeout = setTimeout(later, wait);
};
}

// 검색 API 호출 최적화
const debouncedSearch = debounce(async (query) => {
if (query.trim()) {
const results = await axios.get('/api/search', {
params: { q: query }
});
console.log('검색 결과:', [results.data](http://results.data));
}
}, 300);

// 3. 동시 요청 제한
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); // 최대 3개 동시 요청
Share