양파개발자 실바의 블로그

Axios를 활용한 API 호출용 서비스 모듈

Axios를 활용하여 API를 호출하는 서비스 모듈입니다. 인증이 필요한 API와 인증이 필요 없는 API를 구분하여 사용할 수 있습니다.


1. ApiController

인증이 필요한 대부분의 API-v2 API 호출 시 사용되는 기본 http client object입니다. axios를 사용합니다.

/**
 * 인증이 필요한 대부분의 API-v2 API 호출할때 사용되는 기본 http client object 를 설정
 * 통지 서비스용 NotificationApiController 는 별도로 관리
 */
import axios from 'axios'
import { checkAuthTokens } from 'libraries/AuthProvider'

const ApiController = axios.create({
  baseURL: process.env.NEXT_PUBLIC_EXTERNAL_API_URL,
  timeout: 60000,
  withCredentials: false,
})

ApiController.getErrorMessage = (baseURL, error) => {
  // Service 모듈에서 getErrorMessage() 에 활용됨

  const defaultErrorMessage = '오류가 발생했습니다. 관리자에게 문의하세요.'
  const errorMessage = error.response?.data?.message || defaultErrorMessage

  if (errorMessage == defaultErrorMessage) {
    console.error(`API failed: ${baseURL}`)
    console.error(error)
  } else {
    console.error(`API failed: ${baseURL} - ${errorMessage}`)
  }

  return errorMessage
}

ApiController.listParamKeyRepeatSerializer = (params) => {
  // 쿼리 파라미터에 key:[111,222] 배열 형식이 입력되었을때
  // 기본적으로는 key[]=111&key[]=222 로 전송되기때문에 DRF Filter 에서 이해를 못함.
  // 이를 해결하기위해 실제 전송되는 URL 형태를 key=111&key=222 로 변환하는 로직

  return Object.keys(params)
    .map((key) => {
      const value = params[key]

      // null 또는 빈 문자열인 경우 제외
      if (value === null || value === '') return ''

      // 배열인 경우 처리
      if (Array.isArray(value)) {
        return value
          .filter((val) => val !== null && val !== '') // 배열 값에서도 null 또는 빈 문자열 제외
          .map((val) => `${encodeURIComponent(key)}=${encodeURIComponent(val)}`)
          .join('&')
      }

      // 기본 값 처리
      return `${encodeURIComponent(key)}=${encodeURIComponent(value)}`
    })
    .filter((param) => param !== '') // 빈 문자열인 경우 제외
    .join('&')
}

ApiController.listParamCommaSeparateSerializer = (params) => {
  // 쿼리 파라미터에 key:[111,222] 배열 형식이 입력되었을때
  // 기본적으로는 key[]=111&key[]=222 로 전송되기때문에 DRF Filter 에서 이해를 못함.
  // 이를 해결하기위해 실제 전송되는 URL 형태를 key=111,222 로 변환하는 로직
  // 백엔드 쪽에서는 BaseInFilter 사용하면 됨.

  return Object.keys(params)
    .map((key) => {
      const value = params[key]

      // null 또는 빈 문자열인 경우 제외
      if (value === null || value === '') return ''

      // 배열인 경우 처리
      if (Array.isArray(value)) {
        const commaSeparatedVal = value
          .filter((val) => val !== null && val !== '') // 배열 값에서도 null 또는 빈 문자열 제외
          .map((val) => encodeURIComponent(val))
          .join(',')
        return `${encodeURIComponent(key)}=${commaSeparatedVal}`
      }

      // 기본 값 처리
      return `${encodeURIComponent(key)}=${encodeURIComponent(value)}`
    })
    .filter((param) => param !== '') // 빈 문자열인 경우 제외
    .join('&')
}

ApiController.interceptors.request.use(async (config) => {
  if (typeof window === 'undefined') return config

  // CSR 페이지로부터의 접근일때
  let accessToken = window.__NEXT_DATA__.props?.accessToken
  let refreshToken = window.__NEXT_DATA__.props?.refreshToken
  if (!refreshToken) return config

  // 이미 로그인한 상황일 경우
  try {
    const checkTokenResult = await checkAuthTokens(accessToken, refreshToken)
    if (!checkTokenResult.isAuthorized) {
      throw Error('ApiController - 인증 오류')
    }

    if (checkTokenResult.isRenewed) {
      accessToken = checkTokenResult.newAccessToken
      window.__NEXT_DATA__.props.accessToken = checkTokenResult.newAccessToken
      window.__NEXT_DATA__.props.refreshToken = checkTokenResult.newRefreshToken
    }
  } catch (error) {
    window.location.href = '/auth/login'
    return Promise.reject(error)
  }

  config.headers.Authorization = `Baro ${accessToken}`
  return config
})

ApiController.interceptors.response.use(
  (res) => res,
  function (error) {
    const isBrowser = typeof window != 'undefined'

    if (isBrowser) {
      if (error.response?.status == 401) {
        window.location.href = '/auth/login'
      }
    }

    return Promise.reject(error)
  }
)

export default ApiController

2. Django ViewSet 기반 API 서비스 모듈 템플릿

Django ViewSet 기반 API를 호출하기 위한 서비스 모듈 템플릿입니다. axios 기반이며, 인증이 필요 없는 API를 호출할 때 사용되는 기본 http client object입니다.

/**
 * Django ViewSet 기반 API 용 서비스 코드 템플릿
 *
 * - 이 파일을 복사-붙여넣기하여 쉽게 코드를 작성해보세요 !!
 * - 인증이 필요없는 API 의 경우 PureApiController 를 사용하세요 !!
 * - pages/_template 하위 파일들과 함께 활용하세요~!
 */
import ApiController from 'libraries/ApiController'

export default {
  baseURL: '/cms/products',
  getErrorMessage(error) {
    return ApiController.getErrorMessage(this.baseURL, error)
  },
  async getList(params) {
    return await ApiController.get(this.baseURL, {
      params: params,
    }).then((response) => response.data)
  },
  async getDetail(id) {
    return await ApiController.get(`${this.baseURL}/${id}`).then(
      (response) => response.data
    )
  },
  async create(requestBody) {
    return await ApiController.post(this.baseURL, requestBody).then(
      (response) => response.data
    )
  },
  async update(id, requestBody) {
    return await ApiController.put(`${this.baseURL}/${id}`, requestBody).then(
      (response) => response.data
    )
  },
  async partialUpdate(id, requestBody) {
    return await ApiController.patch(`${this.baseURL}/${id}`, requestBody).then(
      (response) => response.data
    )
  },
}