Codeit Weekly Mission [Week 5] axios로 api요청 날리기

0

Weekly Mission

목록 보기
4/10
post-thumbnail

드디어 위클리 미션에 서버에서 데이터를 동적으로 받아오는 부분을 구현한다. 서버는 코드잇에서 제공한 샘플 서버이다.

5주차 요구사항

기본적으로 이번 주 요구사항은 api를 붙여 서버에서 받아온 데이터를 shared페이지에 띄워주는 것이다. 저번주까지는 mock데이터로 우선 구현했었다. 언젠가는 서버에서 가져와야 할 것 같은 예감이 들어 저번주에 서버에서 받아온 데이터마냥 목데이터를 구성하고, 각 컴포넌트들에게 setter함수를 이용하여 내부 프로퍼티를 설정하게 해 주었는데, 바로 이번주에 서버에서 데이터를 받아 처리하게 요구사항이 나와서 조금 수월할 것 같다. 많은 수정이 필요해 보이진 않는다.

다음은 이번 주 요구사항 중 api를 날려 컴포넌트에 데이터를 전달하는 부분이다.

  • BASE_URL/docs 에 명세된 “/api/sample/folder”에서 받은 데이터가 카드 컴포넌트에 들어가게 수정해 주세요.
  • folder.owner, folder.name 의 데이터도 반영될 수 있도록 수정해 주세요.
  • 상단 네비게이션바에는 “/api/sample/user”에서 받은 데이터를 반영하도록 수정해 주세요.

한마디로 아래 페이지에 보이는 카드들 정보(썸네일, 디스크립션, 날짜 등), 그리고 네브바에 보이는 유저 정보(이메일, 프로필사진)를 서버에서 받아오라는 것이다.

서버 swagger

친절하게 swagger docs도 첨부해주셨다.

axios 가지고 놀기

선택한 이유

사실 fetch api를 사용할 수 있었지만, JSON형식 자동 변환도 해주고, 커스텀 인스턴스를 만들어 각 요청을 알맞게 처리할 수 있는 axios방식이 더 끌렸던 것 같다.

axios 인스턴스 만들기

axios 설치

우선 axios를 사용하기 위해서는 외부로부터 설치하는 과정이 필요하다. 다음 세 가지 방법이 가능하다고 공식문서에서 알려준다.

  • npm 사용하기:

    	$ npm install axios
  • bower 사용하기:

    	$ bower install axios
  • yarn 사용하기:

    	$ yarn add axios
  • jsDelivr CDN 사용하기:

    	<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
  • unpkg CDN 사용하기:

    	<script src="https://unpkg.com/axios/dist/axios.min.js"></script>

나는 가장 간편한 방법인 CDN 방식을 우선 택했다. 프로젝트 규모가 커지면 왠지 npm으로 받아야할 것 같지만, 일단은 npm이 없는 공용 컴퓨터에서도 작업을 많이 하고 있어서 남자답게 CDN으로 가져와준다. 참고로 CDN방식은 런타임에 브라우저에서 필요한 axios소스들을 가져오는 방법이다.

<!-- shared.html -->
...
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
...

인스턴스 생성

axios 인스턴스를 생성하기 위해 다음과 같이 src/service/axios/instance.js파일을 생성하고 안에서 바로 인스턴스 팩토리 함수를 만들어 export해준다.

// src/service/axios/instance.js
export const wrapRequest = (func) => {
  return async (...args) => {
    const res = await func(...args);
    if (res && res.data && res.code && res.code !== 0) {
      throw res;
    } else {
      // 성공하면 data를 return
      return res.data;
    }
  };
};

export const instance = () => {
  const instance = axios.create({
    baseURL: "/*보안을 위해 가림*/",
    timeout: 10000,
    headers: {
      "Content-Type": "application/json",
      Accept: "*/*",
    },
  });

  instance.interceptors.request.use(
    function (config) {
      // 모든 요청 바로 직전에 공통으로 들어갈 작업
      return config;
    },
    function (error) {
      // 요청 에러 처리
      return Promise.reject(error);
    }
  );

  instance.interceptors.response.use(
    function (response) {
      /*
			http status가 20X인 경우
			응답 바로 직전에 대해 작성합니다.
			.then() 으로 이어집니다.
		  */
      return response;
    },
    function (error) {
      /*
			http status가 20X가 아닌 경우
			응답 에러 처리를 작성합니다.
			.catch() 으로 이어집니다.
		*/
      return Promise.reject(error);
    }
  );

  return instance;
};

여기서 wrapRequest함수는 이전 프로젝트에서 사용한 방식인데, 각종 리퀘스트에 대한 캡슐화를 하는데 편리하게 사용할 수 있다.

인스턴스의 interceptors는 요청을 보내거나 응답을 받을 때 해당 요청과 응답을 가로채서 직전, 직후에 공통으로 할 작업들을 콜백함수 형식으로 등록할 수 있다. 보통 요청시에 권한을 확인하는 공통작업 등이 이곳에 들어간다. 일종의 미들웨어와 같은 역할을 한다고 보면 된다. 당장은 사용할 일이 없어서 비워뒀지만 앞으로 사용할 일이 분명히 있을 것이다.

공통 메서드들을 함수로 선언하기

이제 인스턴스를 생성하였으니, 공통된 기본 CRUD 요청 메서드들도 만들어 준다. 다음과 같이 src/service/axios/common.js에 기본 get, post, put, patch, delete메서드를 날리는 함수를 만들어준다. 아까 생성한 인스턴스 팩토리함수를 불러와서 호출시킨 뒤 각 메서드들을 호출하는 비동기 함수를 리턴한다.

// src/service/axios/common.js
import { wrapRequest, instance } from "./instance";

// 기본 post 요청
export const postRequest = wrapRequest(async (url, data) => {
  return instance().post(url, data);
});

// 기본 get 요청
export const getRequest = wrapRequest(async (url, data) => {
  return instance().get(url, { params: data });
});

// 기본 put 요청
export const putRequest = wrapRequest(async (url, data) => {
  return instance().put(url, data);
});

// 기본 patch 요청
export const patchRequest = wrapRequest(async (url, data) => {
  return instance().patch(url, data);
});

// 기본 delete 요청
export const deleteRequest = wrapRequest(async (url) => {
  return instance().delete(url);
});

이 함수들은 앞으로 각종 특수한 요청을 날리는 함수에서 줄기차게 사용될 예정이다.

folder관련, user관련 api 모듈 만들기

이제 위에 정의한 기본 메서드들을 활용해서 특수한 상황의 요청 함수들을 만들어 준다. 다음과 같이 관련있는 요청끼리 모아서 모듈로 분리한다.

// src/service/axios/folderApi.js
import { getRequest } from "./common";

export const getFolderRequest = async () => {
  const response = await getRequest(`/folder`);
  return response;
};
// src/service/axios/userApi.js
import { getRequest } from "./common";

export const getUserRequest = async () => {
  const response = await getRequest(`/user`);
  return response;
};

더 특수한 요청들은 실제 api를 호출하는 부분에서 다루기로 한다. 여기서 만약 getUserProfileImgRequest와 같은 response에서 특수한 데이터만을 가져오는 함수까지 만들면 너무 양이 많아질 것 같아 우선은 이렇게 정의해놓았다. 사용할 준비는 이것으로 마쳤다.

이제 실제 이 api들의 end-user단(shared페이지)에서 api호출을 하고 받아온 데이터 가공 후 컴포넌트까지 전달하면 된다.

인스턴스 메서드 실제 호출해보기

shared.html에서 folder에 대한 요청, user에 대한 요청을 날린 후, 각 웹 컴포넌트들에게 받아온 데이터를 적절히 가공하여 전달해주는 과정을 톺아보자.

우선 shared.html에서 api를 쏘려면 해당 스크립트가 필요할 것 같다. shared.js를 만들어 다음과 같이 의사 코드를 만들어보았다.

import { getFolderRequest } from "./service/axios/folderApi.js";

document.addEventListener("DOMContentLoaded", () => {
  // api 호출을 시행
  const getData = ()=>{
	//api를 호출하여 원하는 대로 가공된 데이터를 가져온다. 
  }

  const sendData = (data)=>{
	// 파라미터로 가공된 데이터를 받아 
	// 원하는 컴포넌트로 보낸다.
  }

  sendData(getData());
});

이 정도 로직이 되겠다.
우선 getData함수만 실행한 뒤 받아온 데이터를 콘솔에 출력해보았다. 다음과 같이 잘 나오는 것을 확인하였다.

이제 받아온 데이터를 예쁘게 가공하여 각 웹 컴포넌트들에게 전달하면 될 듯하다.

비동기 처리를 위해 코드를 몇 번 수정한 결과 다음과 같은 결과물이 나왔다.

import { getFolderRequest } from "./service/axios/folderApi.js";
import { getUserRequest } from "./service/axios/userApi.js";

document.addEventListener("DOMContentLoaded", async () => {
  const sendPropToElem = (elem, data) => {
    elem.prop = data;
  };

  const getCardListProp = (dataList) => {
    return dataList.map((data) => {
      return {
        id: data.id,
        href: data.url,
        thumbnailSrc: data.imageSource ?? "/images/default-thumbnail.svg",
        isLiked: false,
        metadata: {
          description: data.description,
          createdDate: data.createdAt,
        },
      };
    });
  };

  const getFolderInfoContainerProp = (folder) => {
    return {
      folderName: folder.name,
      ownerName: folder.owner.name,
      profileImgSrc: folder.owner.profileImageSource ?? "/images/avatar.png",
    };
  };

  const getGnbProp = (userData) => {
    return {
      isLoggedIn: true,
      profileImgSrc: userData.profileImageSource ?? "/images/profile_img.png",
      username: userData.name,
      email: userData.email,
    };
  };

  const { data: folderData } = await getFolderRequest();
  const cardListProp = getCardListProp(folderData.folder.links);
  const folderInfoContainerProp = getFolderInfoContainerProp(folderData.folder);

  const { data: userData } = await getUserRequest();
  const gnbProp = getGnbProp(userData);

  const cardListElem = document.querySelector("custom-link-card-list");
  const gnbElem = document.querySelector("custom-gnb");
  const folderInfoContainerElem = document.querySelector(
    "custom-folder-info-container"
  );

  sendPropToElem(cardListElem, cardListProp);
  sendPropToElem(gnbElem, gnbProp);
  sendPropToElem(folderInfoContainerElem, folderInfoContainerProp);
});

그런데 DOMContentLoaded 핸들러에 async 붙어 있어도 되는건가... 모르겠다..

최종 결과물..

여기에서 확인하십시다.

사진으로만 보면 대충 이렇다.

0개의 댓글