Axios 코드 재사용하기

Jessie·2024년 7월 9일

프로젝트를 진행하면서 API 연동 작업을 시작했을 때, 데이터가 필요할 때마다 Axios 코드를 때려넣는 짓(…)은 하면 안된다는 지시를 받았다. 대신 중복코드를 줄이고, 혹시라도 코드가 수정될 일이 있다면 더 편하게 수정할 수 있도록 코드를 랩핑해야한다고 말씀해주셨다. 그래서 Axios 코드를 재사용할 수 있도록 랩핑해보았다.

const instance = axios.create({
    baseURL: BASE_URL,
    maxRedirects: 0,
    timeout: 60000,
})

우선 모든 Axios 요청에 공통적으로 들어가는 속성들을 Axios 인스턴스에 넣어 미리 생성해주었다. 이 인스턴스를 불러서 사용할 때는 미리 적용된 설정들을 다시 작성해주지 않아도 돼서 코드의 중복을 줄일 수 있다. 무엇보다 Base URL을 매번 작성해주지 않아도 되는게 정말 편했다.

interface ApiRequest {
    method?: Method
    path?: string
    contentType?: string
    data?: unknown
    params?: unknown
    responseType?: ResponseType
    otherService?: boolean
}

현재 진행하고 있는 프로젝트는 타입스크립트를 사용하고 있기 때문에 Axios 요청을 할 때 넣어주어야 하는 정보들을 모아 인터페이스로 선언해주었다. 다만 http 메서드들마다 다른 형태의 정보를 받으므로 속성들을 전부 옵셔널 프로퍼티로 설정했다. 이렇게 하면 요청마다 받는 객체의 속성이 달라도, 이 인터페이스 안의 속성이기만 하면 타입 오류가 나지 않는다.

import { getAccessToken } from '@src/api/agents/storage'
import instance from '@api/agents/http/axios'

async function AxiosRequest<T>(body: ApiRequest): Promise<T> {
    const { method, path, contentType, data, params, responseType, otherService  } = body

    const access = getAccessToken()
    const t = contentType ?? 'application/json'
    const h = otherService
        ? { 'Content-Type': t }
        : {
            'Authorization': `Bearer ${access}`,
            'Content-Type': t,
        }

    try {
        return await instance.request({
            method,
            url: path,
            headers: h,
            data,
            params,
            responseType: responseType ?? 'json',
        })
    } catch (e) {
        return await Promise.reject(e)
    }
}

export default AxiosRequest

요청을 보내는 코드를 AxiosRequest라는 이름의 함수로 랩핑해주었다. 위에서 선언한 인터페이스는 이 함수가 받는 인자의 타입으로 지정해주었다. 반환값의 타입은 어떻게 할까 고민했는데, 제네릭 타입을 써보라고 하셔서 제네릭을 사용했다. 타입스크립트를 사용하면서 any 타입을 사용하는 상황은 피하고 싶었는데 제네릭 타입을 사용하니 요청을 할때 각 요청이 돌려주는 데이터를 직접 설정해줄 수 있는 점이 좋았다.

함수 내부에서는 인자로 받은 값을 Axios 요청의 파라미터로 넣어준다. 하지만 기본적으로 많이 사용하는 속성들은 직접 적어주지 않아도 자동으로 요청에 포함되도록 해주었다. 토큰의 경우, 자동으로 로컬스토리지에서 토큰을 가져오는 함수를 사용해 헤더에 토큰 설정을 해준다. 만약에 토큰이 들어가면 안되는 요청에는 (ex. S3에 이미지를 보내는 요청) otherService 속성만 true로 해주면 헤더 설정을 쉽게 바꿀 수 있다. 또, 헤더의 Content-type이나 responseType과 같이 일반적으로 많이 사용하는 값이 있는 속성들은 따로 값을 넣어주지 않으면 주로 사용되는 값이 세팅되도록 해주었다.

파라미터가 설정되면 요청을 보낸다. 성공하면 서버로부터 돌아온 응답을 반환하고, 에러가 발생하면 에러 핸들링을 할 수 있도록 거부된 프로미스 객체를 반환한다.

interface ServiceResponse<T> {
    status: number;
    statusText: string;
    headers: AxiosHeaders;
    config: AxiosRequestConfig;
    data: T;
    request?: XMLHttpRequest;
}

Axios 통신이 돌려주는 응답의 타입도 인터페이스로 선언했다. 응답들이 기본적으로 가지고 있는 속성들은 타입을 정해주었고, API마다 다르게 돌려주는 data 속성의 타입만 제네릭 타입으로 설정했다.

Request 속성은 상황에 따라 응답 객체에 포함이 되지 않을 수도 있다고 한다 (요청은 보내졌지만 클라이언트가 응답을 받지 못했거나, 네트워크 오류가 발생한 상황 등). 따라서 옵셔널 프로퍼티로 설정하고, 클라이언트와 서버의 통신에서만 사용하는 코드이기 때문에 XMLHttpRequest 타입으로 설정했다. 브라우저 환경에서는 XMLHttpRequest 객체를 사용하고, Node.js 환경에서는 ClientRequest 객체를 사용한다고 하니 상황에 따라 타입을 정해주면 되겠다.

AxiosRequest<ServiceResponse<ApiSignInResponse>>({
    method: 'post',
    path: '/auth/sign-in/',
    data: {
        email: body.email,
        password: body.password,
    },
})
.then(...)
.catch(...)

이렇게 작성한 코드를 사용해본 예시이다. DataResponseT 뒤에 응답으로 돌아오는 data의 타입을 제네릭으로 넣어주었다.

이렇게 완성한 코드는 서버 정보를 요청하는 함수에 넣어서 재사용했다. 이미지를 S3 서버로 보내는 작업을 하면서 요청에서 헤더의 Authorization을 빼야하는 일이 생겼었는데, 만약에 이렇게 랩핑을 해주지 않았다면 Authorization이 없는 헤더를 가진 인스턴스를 따로 만들어야했었다. 한번 랩핑을 해주니 함수 안의 Axios 코드만 수정하면 되고 Axios를 부르는 곳에서는 수정을 따로 해 줄 필요가 없어서 편했다. 코드의 중복을 줄이고 유지보수성을 높이기 위해 따로 분리하여 재사용을 한다는 개념은 익숙하게 느껴지는데, 왜 개인 프로젝트를 할때는 이렇게 자주 쓰이는 Axios 코드를 분리할 생각을 못했나 싶다. 물론 코드 분리하는 과정이 처음이라 그런지 쉽지는 않았다. 하지만 한번 해봤으니 앞으로는 더 잘할수 있을 것 같다.

profile
주니어 프론트엔드 개발자입니다 😎

0개의 댓글