개발

AxiosInstance를 직접 생성해 Boilerplate 코드 줄이기

2023년 12월 23일

안녕하세요, 반려동물 공동관리 앱 서비스 포잇을 개발하고 있는 펫모리팀의 프론트엔드 김형석입니다 ! 저는 이 팀에 중간 합류하여, React Native를 사용하여 개발하고 있고 앞으로 개발 관련 글들로 여러분들을 찾아봴 예정입니다. 감사합니다 !

프론트엔드 개발자나 웹 개발자로서, 개발할때 가장 시간을 많이 잡아먹는 일 중 하나가 API 호출작업이라고 생각한다. 사용자에게 보여주지는 정보의 폭이 넓을 수록, 그리고 사용자가 접하고 상호작용하는 정보의 양이 많을 수록, 서버로의 HTTP Request 개수 또한 마찬가지로 많아질 수밖에 없다.

매 HTTP Request 마다 서버가 필요로 하는 정보는 다음과 같이 정리할 수 있다.

  1. End Point

  2. HTTP 요청 메서드(HTTP 동사)

  3. Path Parameter로 삽입되어야 하는 인자값

  4. Post와 Put 메서드 호출 시 포함할 수 있는 Data

  5. Authorization 수단(ex. AccessToken)

  6. 호스트 서버 URL

현재 Pawith 프로젝트에서 우리가 사용하고 있는 HTTP 클라이언트는 Axios인데, Axios에서 지향하는 구조를 바탕으로 위에 언급한 모든 정보들을 전달하기 위해선 다음과 같은 구문이 작성되어야 한다.

const postFuncInBaseFormat= async (pathParamValue,dataValue,AccessToken) => { try { 
  let baseURL = '<https://base-url.com>' 
  let endpoint = `/teams/${pathParamValue}/create` 
    let dataObject = {dataField:dataValue} 
      const response = await axios.post(baseURL + endpoint, dataObject, { 
        { headers: { 
          AccessToken: AccessToken 
        } 
      } 
                                       ) 
        const result = response.data 
          return result 
} catch (error) { 
  console.error('Failed to fetch data:', error) 
  //서버 API호출 자체를 실패하였을 때 도달하는 구문 
} 
                                                                            }

많지 않은 API들을 호출할 경우 이러한 구조로 API호출을 진행하여도 상관없지만, 같은 페이지 내에서 적게는 2개 많게는 5개 이상의 API를 한번에 호출하는 상황이 생기면, 굉장히 많은 Boilerplate 코드가 생성되는만큼 개발효율이 떨어지는 문제가 발생한다.

const function1= async () => {
  ... 
} catch (error) { 
  console.error('Failed to fetch data from function1:', error) 
} 
} 
const function2= async () => { 
  ... 
} catch (error) { 
  console.error('Failed to fetch data from function2:', error) 
} 
} const function3= async () => {
  ...
} catch (error) { 
  console.error('Failed to fetch data from function3:', error) 
} 
}

또한 연쇄적으로 다수의 API를 호출하고자 할때, 정확한 디버깅을 위해 API호출 실패 시 에러 위치를 출력하기 위해 try-catch문으로 감싸줘야한다.

이러한 BoilerPlate들을 제거하고 불필요한 작업을 최소화하고자 Axios에서는 axios.create() 함수로 직접 config들을 정의할 수 있는 axiosInstance를 생성할 수 있도록 기능을 제공한다.

const axiosInstance = axios.create({ 
  baseURL: '<https://base-url.com>', 
  timeout: 5000,
}) 
  
export default axiosInstance

위의 구문으로 생성된 axiosInstance는 axios 객체처럼 메서드를 뒤에 붙여 API호출문을 구성할 수 있으며, axios 객체를 그대로 사용했을 때와 달리 baseURL이 일괄적용되어있기에 매 API 호출때마다 baseURL 변수를 정의할 필요가 없어진다.

import axiosInstance from '../../utils/customAxios'

const postFuncUsingAxiosInstance= async (pathParamValue,dataValue,AccessToken) => {
    try {
			let dataObject = {dataField:dataValue}
      const response = await axiosInstance.post(//axios 대신에 axiosInstance import해서 사용
				`/teams/${pathParamValue}/create`, //baseURL 뒤에 붙는 endpoint만 기입!
				dataObject, {
				{
	        headers: {
	          AccessToken: AccessToken
	        }
	      }
			)
      const result = response.data
			return result
    } catch (error) {
      console.error('Failed to fetch data:', error)
			//서버 API호출 자체를 실패하였을 때 도달하는 구문
    }
  }

또한 서버에서는 의도치않은 대상으로부터의 API호출을 막고자, 사용자를 식별하기위해 AccessToken과 같은 보안 매개체를 HTTP Request 과정에서 요구하는데, 이 또한 AxiosInstance에서의 interceptors 기능으로 미리 일괄 적용이 가능하다.

interceptors는 단어 뜻에서 유추할 수 있듯, API호출 중간에서 request나 response를 가로채가는 기능이다.

axiosInstance.interceptors.request.use(
  async (config) => {
    const accessToken = await AsyncStorage.getItem('accessToken')
    if (!accessToken) {
      return config
    } else {
      config.headers['Authorization'] = `${accessToken}`
      return config
    }
  },
  (error) => Promise.reject(error),
)

위의 구문은 모든 API호출문의 헤더에 삽입되는 Authorization 매개체를 미리 일괄적으로 담는 구문이며, 이러한 interceptors 과정을 거치게 되면,

const postFuncUsingAxiosInstance= async (pathParamValue,dataValue) => {
    try {
			let dataObject = {dataField:dataValue}
      const response = await axiosInstance.post(
				`/teams/${pathParamValue}/create`, //baseURL 뒤에 붙는 endpoint만 기입!
				dataObject)
      const result = response.data
			return result
			}
    } catch (error) {
      console.error('Failed to fetch data:', error)
			//서버 API호출 자체를 실패하였을 때 도달하는 구문
    }
  }

별도로 헤더를 작성할 필요가 없어짐에 따라 코드의 길이가 짧아진다.

또한, Axios의 Interceptors는 에러가 발생했을 때 시점에서도 추가적인 작업을 삽입시킬 수 있다.

axiosInstance.interceptors.response.use(
  (response) => response, // 응답이 성공적인 경우 아무것도 하지 않음
  async (error) => {
    console.log('axiosInstance에서 에러 감지', error.config.method, error.config.url, error.response.data.errorCode)
    return Promise.reject(error)
  },
)

위의 코드는 에러가 발생했을 시, 발생한 API의 URL과 메서드, 그리고 에러코드를 로그에 출력시키게 하는 구문인데, 이러한 작업은 API 호출문마다 삽입했던 try {} catch(e){}구문을 완벽히 대체한다.

따라서, AxiosInstance의 수정작업을 거친 후의 최종 API 구문은 다음과 같이 줄어든다.

const postFuncUsingAxiosInstance= async (pathParamValue,dataValue) => {
	let dataObject = {dataField:dataValue}
  const response = await axiosInstance.post(
		`/teams/${pathParamValue}/create`, dataObject)
  return response.data
}

인자값이나 데이터를 요구하지 않는 API 호출문이면 아래와 같이 구문이 굉장히 깔끔해지는 것을 볼 수 있다.

const getFuncUsingAxiosInstance= async () => {
  const response = await axiosInstance.get(`/teams/todos`)
  return response.data
}

다음에는 AxiosInstance의 Interceptor 기능을 사용해, 토큰 재발급을 하는 방법을 다루겠습니다!