Axios 인터셉터 활용

Axios 인터셉터를 활용하여 요청과 응답을 효율적으로 관리하는 방법을 살펴봅니다.

요약

  • Axios 인터셉터를 사용하여 요청 전후에 공통 로직을 실행할 수 있습니다.
  • 인증 토큰을 자동으로 주입하고, 응답 에러를 처리하여 사용자 경험을 개선할 수 있습니다.
  • 상태 관리 라이브러리와 같이 사용할 때의 주의사항을 확인할 수 있습니다.

배경/문제

Axios는 JavaScript에서 HTTP 요청을 관리하기 위한 라이브러리로,_API_와의 상호작용을 간편하게 만들어줍니다. 특히, 인터셉터(interceptor)를 활용하면 요청 및 응답에 대해 공통 로직을 처리할 수 있어, 인증, 에러 처리, 로깅 등 다양한 기능을 구현할 수 있습니다. 하지만, 실무에서는 인터셉터 구현 시 몇 가지 문제점이 발생할 수 있습니다.

  1. React Hooks 사용의 제한: Axios 인터셉터 내부에서 React Hooks를 직접 사용하는 것은 불가능한데, 이는 React Hooks가 컴포넌트의 실행 흐름에서만 동작하기 때문입니다.
  2. 인증 에러 처리: 요청을 보내면서 인증 문제가 발생할 경우, 무한 루프에 빠지거나 반드시 예외 처리가 필요합니다.
  3. 복잡한 상태 관리: 상태 관리 라이브러리와 Axios 인터셉터를 통합할 때 적절한 결정을 내려야 하며, 순환 의존성에 주의해야 합니다.

접근/해결 전략

Axios 인터셉터를 통해 다양한 요구사항을 충족하기 위해서 다음과 같은 접근 방법을 사용할 수 있습니다:

  • Request Interceptor: 서버로 요청이 전송되기 전에 필요한 공통 처리를 구현합니다. 여기에는 인증 토큰 주입, 요청 로깅 등이 포함됩니다.
  • Response Interceptor: 서버로부터 응답이 오기 전에 에러를 처리하거나 응답 로그를 남기는 등의 처리를 일관되게 수행합니다.

이와 같이 인터셉터를 설정하는 코드는 다음과 같습니다:

import axios from 'axios';

const apiBackend = axios.create({
    baseURL: import.meta.env.VITE_API_URL,
    timeout: 10000,
    withCredentials: true,
});

// Request Interceptor
apiBackend.interceptors.request.use(
    (config) => {
        // 요청 전 처리
        return config;
    },
    (error) => {
        // 요청 에러 처리
        return Promise.reject(error);
    }
);

// Response Interceptor
apiBackend.interceptors.response.use(
    (response) => {
        // 응답 성공 처리
        return response;
    },
    (error) => {
        // 응답 에러 처리
        return Promise.reject(error);
    }
);

구현 포인트

Axios 인터셉터를 사용해 실질적인 구현 예제를 통해 좀 더 구체적으로 살펴보겠습니다.

인증 토큰 자동 주입

인증 토큰을 요청 헤더에 자동으로 추가하는 예제입니다:

apiBackend.interceptors.request.use(
    (config) => {
        const token = localStorage.getItem('accessToken');

        if (token) {
            config.headers.Authorization = `Bearer ${token}`;
        }

        return config;
    },
    (error) => Promise.reject(error)
);

이 코드는 로컬 스토리지에서 accessToken을 가져와 요청 헤더에 추가합니다. 이를 통해 모든 API 요청에 대해 사용자 인증을 자동으로 수행할 수 있습니다.

인증 에러 자동 처리

응답에서 인증 에러가 발생했을 때 자동으로 처리하는 방법입니다:

apiBackend.interceptors.response.use(
    (response) => response,
    async (error) => {
        const status = error.response?.status;
        const requestUrl = error.config?.url || '';

        const isAuthRequest = requestUrl.includes('/auth/');

        if ((status === 401 || status === 403) && !isAuthRequest) {
            console.warn(`인증 에러 (${status}): ${requestUrl}`);

            // Zustand 스토어에서 로그아웃 호출
            const { logout, isAuthenticated } = useAuthStore.getState();

            if (isAuthenticated) {
                await logout();
            }
        }

        return Promise.reject(error);
    }
);

이 코드는 401 또는 403 상태 코드를 받았을 때, 인증 요청이 아닌 경우 사용자 로그아웃을 자동으로 수행합니다. 이는 무한 루프를 방지하는 데 도움이 됩니다.

요청/응답 시간 측정

요청 및 응답의 시간을 측정하여 로그를 남기는 방법도 유용합니다:

apiBackend.interceptors.request.use((config) => {
    config.metadata = { startTime: Date.now() };
    return config;
});

apiBackend.interceptors.response.use(
    (response) => {
        const duration = Date.now() - response.config.metadata.startTime;
        console.log(`[API] ${response.config.url} took ${duration}ms`);
        return response;
    },
    (error) => {
        if (error.config?.metadata) {
            const duration = Date.now() - error.config.metadata.startTime;
            console.log(
                `[API Error] ${error.config.url} failed after ${duration}ms`
            );
        }
        return Promise.reject(error);
    }
);

주의사항/트레이드오프

Axios 인터셉터를 사용할 때 고려해야 할 주의사항과 트레이드오프는 다음과 같습니다.

순환 의존성 방지

인터셉터에서 상태 관리 스토어를 사용할 때 순환 의존성을 피하는 것이 중요합니다. 다음과 같은 구조는 문제를 일으킬 수 있습니다:

// apiBackend.ts에서 authStore import 시 주의
// authStore.ts도 apiBackend.ts를 import하면 순환 의존성 발생

이를 방지하기 위해 동적 import 또는 lazy 패턴을 사용하는 것이 좋습니다:

const getAuthStore = () =>
    import('@/stores/authStore').then((m) => m.useAuthStore);

무한 루프 방지

인증 요청을 하는 과정에서 로그아웃 API가 401을 반환할 경우 무한 루프가 발생할 수 있습니다. 따라서 아래와 같이 처리해야 합니다:

apiBackend.interceptors.response.use(
    (response) => response,
    async (error) => {
        const isLogoutRequest = error.config?.url?.includes('/logout');

        if (error.response?.status === 401 && !isLogoutRequest) {
            await logout();
        }

        return Promise.reject(error);
    }
);

마무리

Axios 인터셉터를 통해 요청 및 응답을 효율적으로 처리할 수 있는 여러 가지 해법을 살펴보았습니다. 다음과 같은 체크리스트를 통해 전체적인 구조와 로직을 점검해볼 수 있습니다:

  • 인증 토큰 주입 로직이 올바르게 작동하는가?
  • 오류 응답을 적절하게 처리하고 있는가?
  • 상태 관리 스토어와의 연동에서 순환 의존성 문제가 없는가?
  • 타임아웃 및 응답 속도를 로깅하여 모니터링하고 있는가?

이러한 요소들을 고려하여 Axios 인터셉터를 효과적으로 활용하시길 바랍니다.


© 2024. Chiptune93 All rights reserved.