Axios와 API 통신 — 네트워크 요청 패턴
Axios는 HTTP 요청을 간편하게 처리하는 라이브러리로, 인터셉터와 인스턴스를 통해 API 통신을 체계적으로 관리할 수 있습니다.
fetch API만으로도 API 호출은 가능하지만, 토큰 갱신, 에러 일괄 처리, 요청 취소 등 실무에서 필요한 기능을 체계적으로 관리하려면 Axios가 편리합니다.
설치와 기본 사용
npm install axios
import axios from 'axios';
// 기본 요청
const response = await axios.get('https://api.example.com/users');
console.log(response.data);
// POST 요청
const newUser = await axios.post('https://api.example.com/users', {
name: '홍길동',
email: 'hong@example.com',
});
Axios 인스턴스 설정
// api/client.ts
import axios from 'axios';
import AsyncStorage from '@react-native-async-storage/async-storage';
const apiClient = axios.create({
baseURL: 'https://api.example.com',
timeout: 10000,
headers: {
'Content-Type': 'application/json',
},
});
// 요청 인터셉터 — 토큰 자동 주입
apiClient.interceptors.request.use(
async (config) => {
const token = await AsyncStorage.getItem('accessToken');
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
},
(error) => Promise.reject(error)
);
// 응답 인터셉터 — 토큰 갱신, 에러 처리
apiClient.interceptors.response.use(
(response) => response,
async (error) => {
const originalRequest = error.config;
// 401 에러 시 토큰 갱신 시도
if (error.response?.status === 401 && !originalRequest._retry) {
originalRequest._retry = true;
try {
const refreshToken = await AsyncStorage.getItem('refreshToken');
const { data } = await axios.post('/auth/refresh', { refreshToken });
await AsyncStorage.setItem('accessToken', data.accessToken);
originalRequest.headers.Authorization = `Bearer ${data.accessToken}`;
return apiClient(originalRequest);
} catch (refreshError) {
// 리프레시도 실패 → 로그아웃
await AsyncStorage.multiRemove(['accessToken', 'refreshToken']);
// 로그인 화면으로 이동
}
}
return Promise.reject(error);
}
);
export default apiClient;
API 함수 모듈화
// api/userApi.ts
import apiClient from './client';
export interface User {
id: string;
name: string;
email: string;
avatar: string;
}
export const userApi = {
getAll: () =>
apiClient.get<User[]>('/users').then((res) => res.data),
getById: (id: string) =>
apiClient.get<User>(`/users/${id}`).then((res) => res.data),
create: (data: Omit<User, 'id'>) =>
apiClient.post<User>('/users', data).then((res) => res.data),
update: (id: string, data: Partial<User>) =>
apiClient.put<User>(`/users/${id}`, data).then((res) => res.data),
delete: (id: string) =>
apiClient.delete(`/users/${id}`),
};
// 사용
const users = await userApi.getAll();
const user = await userApi.getById('123');
에러 핸들링
import axios, { AxiosError } from 'axios';
interface ApiError {
message: string;
code: string;
}
async function handleApiCall() {
try {
const data = await userApi.getAll();
return data;
} catch (error) {
if (axios.isAxiosError(error)) {
const axiosError = error as AxiosError<ApiError>;
if (axiosError.response) {
// 서버가 응답을 반환한 경우 (4xx, 5xx)
const status = axiosError.response.status;
const message = axiosError.response.data?.message;
switch (status) {
case 400: Alert.alert('오류', message || '잘못된 요청입니다'); break;
case 403: Alert.alert('권한 오류', '접근 권한이 없습니다'); break;
case 404: Alert.alert('오류', '데이터를 찾을 수 없습니다'); break;
case 500: Alert.alert('서버 오류', '잠시 후 다시 시도해주세요'); break;
}
} else if (axiosError.request) {
// 요청은 보냈지만 응답을 받지 못한 경우
Alert.alert('네트워크 오류', '인터넷 연결을 확인해주세요');
}
}
throw error;
}
}
요청 취소
import { useEffect, useRef } from 'react';
import axios from 'axios';
function SearchScreen() {
const abortControllerRef = useRef<AbortController>();
const search = async (query: string) => {
// 이전 요청 취소
abortControllerRef.current?.abort();
abortControllerRef.current = new AbortController();
try {
const { data } = await apiClient.get('/search', {
params: { q: query },
signal: abortControllerRef.current.signal,
});
setResults(data);
} catch (error) {
if (!axios.isCancel(error)) {
console.error('검색 에러:', error);
}
}
};
// 컴포넌트 언마운트 시 취소
useEffect(() => {
return () => abortControllerRef.current?.abort();
}, []);
return <TextInput onChangeText={search} placeholder="검색..." />;
}
파일 업로드
async function uploadImage(uri: string) {
const formData = new FormData();
formData.append('image', {
uri,
type: 'image/jpeg',
name: 'photo.jpg',
} as any);
const response = await apiClient.post('/upload', formData, {
headers: {
'Content-Type': 'multipart/form-data',
},
// 업로드 진행률
onUploadProgress: (progressEvent) => {
const percent = Math.round(
(progressEvent.loaded * 100) / (progressEvent.total ?? 1)
);
console.log(`업로드: ${percent}%`);
},
});
return response.data.url;
}
fetch vs Axios 비교
| 기준 | fetch | Axios |
|---|---|---|
| 내장 여부 | 내장 | 설치 필요 |
| JSON 파싱 | 수동 .json() | 자동 |
| 인터셉터 | 없음 | 지원 |
| 요청 취소 | AbortController | AbortController |
| 타임아웃 | 직접 구현 | 설정만 |
| 진행률 | 지원 안 함 | 지원 |
| 에러 처리 | 4xx도 resolve | 4xx/5xx reject |
정리
- Axios 인스턴스 를 만들어 baseURL, 타임아웃, 헤더를 중앙 관리하세요
- 인터셉터 로 토큰 주입, 토큰 갱신, 에러 일괄 처리를 구현하세요
- API 함수를 도메인별로 모듈화 하면 유지보수가 편합니다
- 검색처럼 빈번한 요청에는 요청 취소 패턴을 적용하세요
- React Query와 함께 사용하면 캐싱, 로딩 상태까지 자동 관리됩니다
댓글 로딩 중...