자바스크립트와 HTTP 통신에서 가장 많이쓰이는 라이브러리 중 하나인 Axios는 여러 편리한 기능들을 제공하고 있습니다. Axios 여러 편리한 기능을 이용하지 않는다면 자바스크립트의 기본 스펙인 fetch() 함수를 이용하는 것과 다를 바가 없죠. 그렇다면 Axios의 편리한 기능을 한 번 알아 볼까요?

출처: Aixos 공식 문서

인스턴스(instance)로 모듈화하기

Axios의 편의 기능 중 하나는 새로운 Axios 인스턴스를 만들 수 있다는 것입니다. 여기서 말하는 인스턴스는 객체 지향에서 말하는 인스턴스와 동일합니다. 동일한 설정을 공유해야 하는 경우, 서로 다른 기본 구성을 가진 요청을 만들어야 하는 경우에 Axios를 사용하면 각각의 설정과 구성을 가진 HTTP 클라이언트 인스턴스를 만들어낼 수 있죠. 예를 들어 서로 다른 url을 가진 두 개의 백엔드 서버에 요청을 보내는 웹 앱에서 기본 주소로 각각의 백엔드 도메인 주소를 가지는 두 개의 Axios 인스턴스를 만들어 사용할 수 있습니다.

 

다음은 Axios의 인스턴스를 만드는 예시입니다. Axios의 crate메서드를 통해 새로운 Axios 인스턴스를 만들 수 있죠.

const instance = axios.create({
  baseURL: 'https://api.example.com',
  headers: { "my-header":"foo" }
});

보시다시피 사용법은 아주 간단합니다. baseURL을 지정하여 해당 인스턴스가 보내는 요청은 기본적으로 지정된 주소를 기반으로 작동합니다. 이는 요청할 때마다 전체 URL을 지정하는 번거로움을 줄여줍니다. 예를 들어, baseURL을 https://api.example.com으로 설정하면 인스턴스에서 /users로 요청을 보내면 실제로 https://api.example.com/users로 요청이 전송됩니다.

 

headers는 말 그대로 HTTP 요청의 header를 설정할 수 있습니다. 해당 인스턴스의 모든 요청은 동일한 헤더를 가지게 되죠. 이는 인스턴스의 사용 목적에 따라 달라지겠지만 보통 인증 토큰이나 사용자 에이전트와 같은 일반적인 헤더를 설정합니다.

인터셉터(interceptor)

Axios의 인스턴스는 단지 귀찮음을 줄여주는 기능만 있는 것은 아닙니다. Axios의 편리한 기능은 바로 인터셉터입니다. 이를 통해 각 HTTP 요청이 then 또는 catch로 처리되기 전에 요청과 응답을 가로챌수 있습니다. interceptor는 다음과 같이 설정할 수 있습니다. 다음은 제가 SSAFY 프로젝트를 진행하면서 실제로 작성한 코드의 일부만 가져와 수정한 것입니다. 전역 상태 관리 라이브러리인 zustand와 함께 사용하며 모든 요청마다 현재의 accessToken을 추가해 주는 코드이죠.

인터셉터를 설정하여 token에 신경끄기

import axios from "axios";
import { useUser } from "../zustand/userStore";

const URL = process.env.API_URL;
export const axiosInstance = axios.create({
    baseURL: URL,
    withCredentials: true,
});


axiosInstance.interceptors.request.use(function (config) {
    // 모든 요청에 accessToken을 담아준다.
    config.headers.Authorization = useUser.getState().accessToken;
    return config;
});

axiosInstance.interceptors.response.use(function (response) {
    return response;
}, async function (error) {
    const originalRequest = error.config;

    // 에러 상태 코드가 401이고, originalRequest에 refreshToken이 존재하며
    // 이전에 재시도를 하지 않은 경우에만 실행
    if (error.response.status === 401 && originalRequest && !originalRequest._retry) {
        originalRequest._retry = true;

        try {
            // 새로운 accessToken을 얻기 위한 API 요청
            const newAccessToken = await getNewAccessToken();

            // 얻은 새로운 accessToken을 사용하여 요청의 Authorization 헤더를 업데이트
            originalRequest.headers.Authorization = newAccessToken;

            // 새로운 accessToken을 사용하여 기존 요청을 다시 보낸다.
            return axiosInstance(originalRequest);
        } catch (error) {
            // 새로운 accessToken을 얻는 동안에도 에러가 발생하는 경우
            console.error("새로운 accessToken을 얻는 동안에 에러가 발생했습니다.", error);
            return Promise.reject(error);
        }
    }

    // 401 에러가 아니거나, originalRequest가 없거나, 이미 재시도를 한 경우에는 그냥 에러를 반환
    return Promise.reject(error);
});

// 새로운 accessToken을 얻기 위한 비동기 함수
async function getNewAccessToken() {
    // 여기에 refreshToken을 사용하여 새로운 accessToken을 얻기 위한 API 요청을 추가
    // aixosinstance가 아닌 다른 인스턴스를 사용해야 한다.
}

interceptors.request.use를 통해 axiosInstance 인스턴스로 보내진 모든 HTTP 요청에 개입합니다. config에는 요청을 보내는 모든 내용이 담겨 있는데 그 중 Authorization header를 User Store의 accessToken으로 추가한 뒤 요청이 이루어지도록 만들었습니다. 이제 해당 Axios 인스턴스를 사용하는 모든 곳에서 token을 신경 쓸 필요가 없어졌습니다.

 

물론 response에서도 interceptor를 설정할 수 있습니다. 특히 accessToken이 만료되었을 때, 자동으로 새롭게 발급받도록 만들 수 있습니다. 각각의 interceptors.(request || response).use의 두번째 인자는 오류상황에서 실행되는 콜백 함수입니다. 추가적으로 응답의 경우 200번대 상태 코드를 제외한 모든 응답에서 실행됩니다. 보통 accessToken이 만료되었다면 서버는 401 상태코드를 보낼테니 401응답을 받았다면 앞서 말한 방식대로 토큰을 새로 발급받게 만들 수 있죠.

 

이렇게 인터셉터를 사용한다면 해당 인스턴스(Axios 전체에도 intercepter를 설정할 수도 있습니다.)를 사용한 모든 요청에서 Token에 대해 전혀 신경쓰지 않아도 되죠.

 

그리고 필요하지 않은 interceptor는 다음 코드를 통해 삭제할 수 있습니다.

const myInterceptor = axios.interceptors.request.use(function () {/*...*/});
axios.interceptors.request.eject(myInterceptor);

참고 자료

Axios 공식문서, [Axios 인스턴스, 인터셉터]

+ Recent posts