Axios는 HTTP 요청을 간편하게 처리하는 라이브러리로, 인터셉터와 인스턴스를 통해 API 통신을 체계적으로 관리할 수 있습니다.

fetch API만으로도 API 호출은 가능하지만, 토큰 갱신, 에러 일괄 처리, 요청 취소 등 실무에서 필요한 기능을 체계적으로 관리하려면 Axios가 편리합니다.


설치와 기본 사용

BASH
npm install axios
TSX
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 인스턴스 설정

TSX
// 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 함수 모듈화

TSX
// 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');

에러 핸들링

TSX
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;
  }
}

요청 취소

TSX
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="검색..." />;
}

파일 업로드

TSX
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 비교

기준fetchAxios
내장 여부내장설치 필요
JSON 파싱수동 .json()자동
인터셉터없음지원
요청 취소AbortControllerAbortController
타임아웃직접 구현설정만
진행률지원 안 함지원
에러 처리4xx도 resolve4xx/5xx reject

정리

  • Axios 인스턴스 를 만들어 baseURL, 타임아웃, 헤더를 중앙 관리하세요
  • 인터셉터 로 토큰 주입, 토큰 갱신, 에러 일괄 처리를 구현하세요
  • API 함수를 도메인별로 모듈화 하면 유지보수가 편합니다
  • 검색처럼 빈번한 요청에는 요청 취소 패턴을 적용하세요
  • React Query와 함께 사용하면 캐싱, 로딩 상태까지 자동 관리됩니다
댓글 로딩 중...