Post

[GitPulse #2] 구조 설계: GitHub 조직 API 연동

[GitPulse #2] 구조 설계: GitHub 조직 API 연동

✅ 오늘의 목표

  • GitHub 조직 API 연동을 위한 React Query 커스텀 훅 작성
  • 사용자 username(localStorage) 기반으로 조직 리스트 불러오기
  • 사이드바 내 반복 코드 제거 및 동적 렌더링

🧩 문제 상황 / 배경

다른 팀원이 Github 로그인 기능을 구현한 상태였고, 사용자 인증은 JWT 기반으로 처리되고 있었습니다.
로그인 후 사용자의 조직 정보를 불러오고 사이드바에 동적으로 반영하려는 과정에서 다음과 같은 필요가 있었습니다:

  • GitHub API를 통해 사용자 조직 리스트를 가져와야 했습니다.
  • 로그인 후 받은 JWT 토큰에서 username을 추출해 스토리지에 저장해야 했습니다.
  • 사이드바 메뉴가 반복적인 코드로 구성되어 있어, 동적으로 렌더링할 수 있도록 개선이 필요했습니다.

🛠️ 해결 과정 / 코드 정리

🔸 1. GitHub 조직 정보 불러오기 (React Query)

src/apis/useOrganizationApi.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import { useQuery } from "@tanstack/react-query";
import axios from "axios";

const BASE_URL = "https://api.github.com";

// 사용자 이름으로 조직 불러오기
export const getOrganizationsByUser = async (username) => {
  try {
    const res = await axios.get(`${BASE_URL}/users/${username}/orgs`);
    return res.data;
  } catch (error) {
    console.log("조직 가져오기 실패", error);
  }
};

export const useOrganizationList = (username) => {
  return useQuery({
    queryKey: ["OrganizationList", username],
    queryFn: async () => {
      try {
        const data = username && (await getOrganizationsByUser(username));
        return data.reverse(); // 최신순 반영
      } catch (err) {
        console.log("", err);
      }
    },
    staleTime: 1000 * 60 * 10, // 10분 동안 캐시된 데이터를 사용함
    retry: 1,
  });
};
  • username을 기반으로 조직 목록을 가져오는 함수 작성
  • staleTime을 10분으로 설정해 캐시 유지
  • .reverse()를 사용해 최신 조직부터 렌더링

React Query를 사용한 이유는 ??

기존에 axios만 사용한다면 다음과 같은 추가 구현이 필요했다:

  • 로딩 상태 관리 (isLoading)
  • 에러 처리 (try/catch)
  • 동일 요청에 대한 캐싱 / 리패칭 제어
  • refetch, prefetch, stale time 등 다양한 서버 상태 관리

반면, React Query는 이러한 서버 상태 로직을 선언적으로 처리할 수 있어 코드가 간결해지고 유지보수도 쉬워진다. 특히 GitHub API처럼 빈번하게 데이터를 가져오는 경우 자동 캐싱 및 리패칭 제어 기능이 매우 유용했다.

💡 React Query는 기본적으로 메모리에만 캐시를 저장하지만, persistQueryClient 플러그인을 사용하면 캐시를 영구 저장소(localStorage, sessionStorage 등)에 유지할 수 있다

GitHub Organizations 응답 데이터 구조


🔸 2. JWT 토큰에서 사용자 이름 추출 및 저장

src/pages/LoginPage.jsx

1
2
3
4
5
6
7
8
9
10
11
12
13
14
useEffect(() => {
  const handleMessage = (event) => {
    const token = event.data;
    if (typeof token === "string" && token.split(".").length === 3) {
      localStorage.setItem("jwt", token);
      const payload = JSON.parse(atob(token.split(".")[1]));
      localStorage.setItem("username", payload.login);
      setSocialUser(payload);
      navigate("/");
    }
  };
  window.addEventListener("message", handleMessage);
  return () => window.removeEventListener("message", handleMessage);
}, []);

로그인 로직 내에서 JWT가 브라우저에 전달되면, username을 추출해 localStorage에 저장하도록 처리

  • GitHub 로그인 후 메시지로 전달된 JWT 토큰을 수신
  • 토큰의 payload에서 login 값을 추출해 username으로 저장

🔸 3. 반복적인 사이드바 메뉴 동적 렌더링으로 개선 - src/common/SideBar.jsx

기존 코드 (하드코딩 방식)

1
2
3
4
<CustomNavLink to={"/organization"} label={"Organization_1"} icon={"bi-people-fill"} />
<CustomNavLink to={"/organization2"} label={"Organization_2"} icon={"bi-people-fill"} />
<CustomNavLink to={"/organization3"} label={"Organization_3"} icon={"bi-people-fill"} />
...

개선된 코드

1
2
const username = localStorage.getItem("username");
const { data: groupList, isLoading, isError } = useOrganizationList(username);
1
2
3
4
5
6
7
8
{groupList?.map((group) => (
    <CustomNavLink
    key={group.id}
    to={`/org/${group.id}/${group.login}`}
    label={group.login}
    icon={"bi-people-fill"}
    />
))}
  • groupList를 기반으로 메뉴를 동적으로 생성 -> 반복 제거 및 유지보수 용이성 향상

🖼️ 작업 결과물


💡 개발 인사이트

1. React Query 캐싱은 새로고침 시 초기화된다

  • React Query는 기본적으로 메모리 기반 캐싱을 제공한다.
  • 따라서 브라우저를 새로고침하면 메모리가 초기화되면서 다시 API 요청이 발생한다.
1
2
// 해결 방법
import { persistQueryClient } from '@tanstack/react-query-persist-client';

지속 가능한 캐싱을 원한다면, persistQueryClient 플러그인을 통해 localStorage, sessionStorage 등에 캐시를 저장할 수 있다.

2. username 비어있을 때 쿼리 방지: enabled 옵션 활용

  • useQuery에서 username이 없는 경우에도 불필요한 요청이 발생할 수 있다.
  • 이런 경우 enabled 옵션을 활용하면 깔끔하게 방지할 수 있다.
1
2
3
4
5
useQuery({
  queryKey: ["OrganizationList", username],
  queryFn: () => getOrganizationsByUser(username),
  enabled: !!username, // username이 있을 때만 호출
});

React Query는 불필요한 요청을 방지하고, 조건부 요청을 명확하게 선언할 수 있는 구조를 제공한다.

3. GitHub API는 퍼블릭 조직만 반환한다

  • 현재 연동된 GitHub API (GET /users/:username/orgs)는 퍼블릭 조직 정보만 반환한다.
  • 비공개 조직 정보를 받기 위해서는 사용자 인증 시 read:org 범위의 OAuth 토큰이 필요합니다.

향후 비공개 조직 데이터가 필요할 경우, 백엔드에서 인증 범위를 추가하거나 토큰 권한 관리가 필요 !!


결과 및 느낀 점

  • React Query를 활용해 비동기 호출에 따른 로딩/에러 처리를 고려하는 것의 편리함을 느낄 수 있었습니다.
  • 사이드바 구성 코드를 반복 없이 동적으로 생성하면서 컴포넌트 구조의 중요성을 다시 한번 느꼈습니다.
  • GitHub REST API는 직관적인 URL 구조와 잘 정리된 문서 덕분에 처음 사용하는 입장에서도 큰 어려움 없이 연동할 수 있었습니다.
  • 사용자 기반 조직 리스트를 불러오는 과정에서 API 요청 결과가 어떤 구조로 오는지를 확인하고, 필요한 필드만 추려내어 사용하는 경험이 유익했습니다.

Reference

이 포스트는 아래 게시글의 정보 및 이미지가 사용되었습니다.

  1. TanStack Query 공식 문서
  2. GitHub REST API - Get user organizations

END

This post is licensed under CC BY 4.0 by the author.