본문 바로가기

카테고리 없음

axios 모듈화에 대한 이해

발단:

웹 개발을 하면서 계속해서 사용하게 될 axios 라이브러리에 대해, 효율적으로 모듈화하여 사용하는 방법을 제대로 이해하고 싶었다.


내용:

기존에 사용하던 방식

- src/api/index.js 파일에 path를 객체로 저장함

- 서버로 요청을 보낼 각 컴포넌트에서 axios를 임포트해서 try catch문으로 사용함

// src/api/index.js

const URL = 'http://localhost:8080/api';
const USERS = '/user';
const DIARY = '/diary';

const api = {
  user: {
    signUp: () => URL + USERS + '/sign-up',
    findUserId: (email) => URL + USERS + `/find-id/${email}`,
  },
  diary: {
    postDiary: () => URL + DIARY,
    deleteDiary: (diaryId) => URL + DIARY + `/${diaryId}`,
  },
};

export default api;
// api 요청을 보낼 곳
  
import axios from 'axios';
import api from '../../../api/api';
  
// 일기장 삭제 요청
const fetchDeleteDiary = async () => {
  try {
    await axios.delete(api.diary.getDiaryInfo(diaryId));

    navigate('/diary', { state: { filter: false } });
  } catch (err) {
    console.error(err);
  }
};

내가 하고 싶었던 것

-  axios instance를 사용하고 싶었음

- try catch문을 매번 쓰고 싶지 않았음


구현과정

1단계

일단 src/apis/index.js 에 axios instance를 생성했다.

만들려는 서비스 중 일부 페이지는 로그인 하지 않은 유저도 접근 가능하기 때문에, 구분을 위해 defaultInstance와 authInstance로 나누어 만들었다. (아직 로그인 구현 전이라 나중을 위해 일단 구분해둠)

// src/apis/index.js

import axios from "axios";

const BASE_URL = 'http://localhost:8080/api'

const accessToken = "test";

// accessToken이 필요 없는 요청시
const axiosApi = (url, options) => {
  const instance = axios.create({ baseURL: url, ...options });
  return instance;
};

// accessToken이 필요한 요청시
const axiosAuthApi = (url, options) => {
  const instance = axios.create({
    baseURL: url,
    headers: { Authorization: accessToken },
    ...options,
  });

  return instance;
};

export const defaultInstance = axiosApi(BASE_URL);
export const authInstance = axiosAuthApi(BASE_URL);

 

src/apis/api.js 파일에 기존 방식대로 path들을 모아주었다. 기존과 차이점은 base url을 매번 적지 않아도 되게 되었다는 것.

// src/apis/api.js

const USERS = "/member";

const api = {
  user: {
    login: (type) => `/auth/login/${type}`,
    resister: () => USERS + "/resister",
  },
};

export default api;

 

axios 요청은 만든 인스턴스를 임포트한 뒤 try catch문으로 사용하게 되었다.

// api 요청을 보내는 곳

import { defaultInstance } from "../../../apis";
import api from "../../../apis/api";

const kakaoLogin = async (code) => {
    try {
      const res = await defaultInstance.get(api.user.login("kakao"), {
        params: { code: code },
      });

      if (res.data.accessToken) navigate("/");
      else
        navigate("/signup", {
          state: { email: res.data.email, type: res.data.type },
        });
    } catch (err) {
      console.error(err);
    }

인스턴스를 사용한다는 것 말고는 바뀐 건 없다.


2단계

만든 axios instance에 interceptor를 적용했다.

interceptor는 공식문서에 아래와 같이 설명되어 있다.


then 또는 catch로 처리되기 전에 요청과 응답을 가로챌수 있습니다.

// 요청 인터셉터 추가하기
axios.interceptors.request.use(function (config) {
    // 요청이 전달되기 전에 작업 수행
    return config;
  }, function (error) {
    // 요청 오류가 있는 작업 수행
    return Promise.reject(error);
  });

// 응답 인터셉터 추가하기
axios.interceptors.response.use(function (response) {
    // 2xx 범위에 있는 상태 코드는 이 함수를 트리거 합니다.
    // 응답 데이터가 있는 작업 수행
    return response;
  }, function (error) {
    // 2xx 외의 범위에 있는 상태 코드는 이 함수를 트리거 합니다.
    // 응답 오류가 있는 작업 수행
    return Promise.reject(error);
  });

즉 request 또는 response 요청 전에 intercept해서 먼저 실행시킬 코드를 작성할 수 있다.

즉 try catch문을 대신할 수 있다!

만든 axios instance를 리턴하기 전에 interceptor를 걸어서 response.data를 콘솔에 출력 후 리턴하고, 에러 발생시 에러를 콘솔에 찍도록 했다. 

response가 아니라 response.data를 리턴한 건 매번 .data 치기 귀찮아서...ㅎㅎ

// src/apis/index.js

// accessToken이 필요 없는 요청시
const axiosApi = (url, options) => {
  const instance = axios.create({ baseURL: url, ...options });

  // 성공시 콘솔에 response.data 출력
  // 에러시 콘솔에 에러 출력
  instance.interceptors.response.use(
    (response) => {
      console.log(response.data);
      return response.data;
    },
    (error) => {
      console.error(error);
    }
  );

  return instance;
};

그럼 실제 요청하는 부분의 코드는 이렇게 바뀐다.

// api 요청을 보내는 곳

import { defaultInstance } from "../../../apis";
import api from "../../../apis/api";

const kakaoLogin = async (code) => {
  const res = await defaultInstance.get(api.user.login("kakao"), {
    params: { code: code },
  });

  if (res.accessToken) navigate("/");
  else
    navigate("/signup", {
      state: { email: res.email, type: res.type },
    });
}

3단계

그런데 이렇게 해서 프론트 팀원에게 설명을 해 주려니까, try catch문이 없어진 건 매우 좋은데 매번 defaultInstance를 임포트해서 사용해야 하나 싶어졌다. 어차피 api path 때문에 api.js를 임포트할 건데 그냥 거기서 요청을 보내면 안 되려나?

 

그래서 src/apis/api.js에서 모든 axios 요청을 보낼 수 있도록 바꿨다.

// src/apis/api.js

import { defaultInstance } from ".";

const api = {
  user: {
    login: (type, params) => defaultInstance.get(`/auth/login/${type}`, params),
  },
};

export default api;
// api 요청을 보내는 곳 

import api from "../../../apis/api";

const kakaoLogin = async (code) => {
  const res = await api.user.login("kakao", { params: { code: code } });
  if (res.accessToken) navigate("/");
  else
    navigate("/signup", {
      state: { email: res.email, type: res.type },
    });
};

아주 간단해졌다! 마음에 든다!

 

이제 로그인 구현을 하면서 defaultInstance랑 authInstance에서 동일하게 작성될 코드들을 어떻게 하나로 합칠 수 있을지 생각해 보고, 마음에 들게 만들어 봐야겠다.

 

끝.