본문 바로가기
4주차

API 폴더? 서비스 폴더? 구조 분리가 중요한 이유

by earlchive 2025. 5. 13.

안녕하세요. AT SOPT 36기 웹 YB 조성하입니다!

 

프론트엔드에서 API 연동은 아주 기본적이지만 중요한 작업입니다.

프로젝트 초반에는 그냥 axios 한 줄로 데이터를 가져오고, 컴포넌트에서 바로 쓰는 게 당연하게 느껴질 수 있어요. 하지만 프로젝트가 커지고 연동하는 API가 많아지기 시작하면 슬슬 유지보수 지옥이 펼쳐집니다...... 

로딩 상태 관리, 에러 처리, 응답 데이터 가공, 재사용 등 생각보다 많은 일들이 겹쳐 혼란스러운 경우가 생기기도 하는 것 같아요.

 

 

그래서 API 관련 코드를 계층적으로 나누는 구조가 중요해집니다!!!

 

흔히 쓰이는 구조로는 api/, services/, hooks/ 폴더로 분리하는 방식이 있다고 하는데요, 이번 주차 아티클에서는 이 세 가지 폴더가 각각 어떤 역할을 하고, 왜 구조를 분리하는게 좋은지, 그리고 실무에서 어떻게 쓰면 되는지 가볍게 살펴보려고 합니다.

 


API 연동을 위한 주요 폴더 구조 비교

api/

api/ 폴더는 서버와 직접 통신하는 가장 낮은 계층입니다. 여기서는 HTTP 메서드(GET, POST 등)를 이용해 실제로 데이터를 가져오거나 전송하는 역할을 합니다. 일반적으로 axios 인스턴스를 사용하는 파일들도 여기에 들어갑니다.

장점
- 모든 API 요청이 모여 있어서 관리 쉬움
- 엔드포인트 변경이나 공통 헤더 설정 같은 작업을 한 곳에서 처리 가능
단점
- 응답 데이터의 가공이 이곳에 섞이면 역할이 모호해짐
- 상태 관리나 UI와의 연결을 여기에 하게 되면 유지보수가 어려워짐

 

services/

services/ 폴더는 말 그대로 비즈니스 로직을 담당합니다. api/로부터 데이터를 받아와 필요한 형태로 가공하고, 여러 API 요청을 조합해서 사용할 수도 있습니다. 예를 들어, 사용자 정보를 받아온 뒤 날짜 형식을 바꾸거나, 특정 필드를 추려내는 등의 작업이 여기서 일어납니다.

장점
- UI와 API를 분리하는 계층으로, 코드 가독성과 재사용성 높여줌
- 테스트 코드 작성 용이
단점
- 역할이 명확하지 않으면 단순한 중복 계층처럼 보일 수 있음
- 이름을 잘못 짓거나 기능이 퍼지면 오히려 혼란 야기

 

hooks/

React에서는 데이터를 화면에 뿌려주기 위해 상태 관리가 필수인데, 이때 커스텀 훅을 만들어 사용하는 게 일반적입니다. hooks/ 폴더에는 주로 useUser, usePosts처럼 데이터를 가져와 상태로 관리해주는 훅을 정의합니다.

 

장점
- 컴포넌트에서 데이터 로직이 분리되어 훨씬 깔끔해짐
- React Query, SWR 같은 라이브러리와 함께 사용하기 좋음
단점
- 훅 안에서 API 호출 + 응답 가공 + 상태 처리까지 모두 해버리면 재사용이 어려워지고 테스트도 어려워짐
 
 

service/까지 꼭 나눠야 할까?

실제로 api/ hooks/는 많이 보는데, services/까지 나누는 건 처음 보는 사람도 있을 수 있어요. "이거 꼭 필요한가?" 싶을 수도 있습니다. (저도 그랬거든요)

 

결론부터 말하면 프로젝트 규모가 작다면 굳이 없어도 됩니다. 하지만 규모가 커지거나 유지보수할 팀원이 늘어날수록 service/ 레이어의 효과는 점점 커집니다!

 

왜냐하면 ...

api/는 단순히 서버에 요청을 보내고 응답을 받을 뿐임
그런데 어떤 API는 응답을 받아서 날짜를 포맷해야 할 수도 있고,
어떤 API는 여러 번 호출해서 하나의 결과로 합쳐야 할 수도 있음

 

이런 도메인 비즈니스 로직을 처리하는 곳이 service/입니다.

또한 테스트 코드 작성 시에도 api는 axios/fetch 같은 외부 의존성이 있어서 mocking이 필요하지만, service는 대부분 순수 함수처럼 만들어지기 때문에 테스트하기 훨씬 수월합니다.

 

즉, service UI와 API 사이의 중간 다리 역할을 하며, 변경 가능성이 큰 API와 UI를 효과적으로 분리해주는 계층이라고 보면 됩니다. 코드가 복잡해지기 시작한다면, 분리해서 사용해보는 것을 추천드려요!

 
 

실제로 어떻게 나누는 걸까?

// api/user.ts

export const getUser = () => axios.get<User>('/users/me');
// services/userService.ts

export const fetchUserProfile = async () => {
  const { data } = await getUser();
  return {
    ...data,
    formattedJoinDate: new Date(data.joinedAt).toLocaleDateString(),
  };
};
// hooks/useUser.ts

export const useUser = () => {
  return useQuery(['user'], fetchUserProfile);
};

 

이렇게 각 계층의 역할이 분리되어 있으면, 나중에 API 구조가 바뀌더라도 api service만 손보면 되고, UI 쪽은 그대로 둘 수 있어요. 또 테스트도 service 단에서 mocking으로 충분히 커버할 수 있어서 유리합니다.

 

 

왜 이렇게 분리해야 할까?

이런 구조적 분리는 단순히 "코드가 깔끔해 보여서"가 아니라, 실제로 개발 생산성과 유지보수성에 큰 영향을 줍니다.

 

  • 유지보수성: 서버 API가 변경되면 api/ service/만 수정하면 되고, 컴포넌트는 그대로 둘 수 있어요.
  • 의존성 분리: 컴포넌트는 훅만 알면 됩니다. axios든 fetch든, 백엔드가 REST든 GraphQL이든 알 필요가 딱히 없어집니다.
  • 테스트 편의성: 비즈니스 로직이 service에 있으니, 해당 부분만 따로 테스트할 수 있습니다. 훅이나 컴포넌트까지 끌어오지 않아도 됩니다.

 

이런 구조는 협업 시에 역할을 명확히 나눌 수 있어서 여러 개발자가 동시에 작업할 때 충돌을 줄여줍니다.

 

 

실수와 안티패턴

아무리 구조를 잘 나누었다고 해도, 여전히 자주 볼 수 있는 실수들이 있는데요...

 

훅에서 모든 걸 처리

API 호출, 데이터 가공, 로딩과 에러 상태 처리까지 전부 넣다 보면 훅이 지나치게 무거워지고, 재사용이 어려워지며 테스트도 복잡해집니다.

해결 방법은 역할을 분리하는 것입니다. API 호출과 데이터 가공은 api service로 나누고, 훅은 그 결과를 단순히 가져와 상태로 관리하는 역할만 맡도록 해야 해요.

 

모든 API를 하나의 파일에 몰아넣기

파일이 커지면 누가 어떤 API를 쓰는지 파악하기 어렵고, 충돌도 자주 발생해요.

따라서 도메인 단위로 나눠서 관리하는 게 훨씬 낫습니다. 예를 들어 api/user.ts, api/post.ts처럼 쪼개면 훨씬 관리가 수월합니다.

 

axios를 컴포넌트에서 직접 호출

개발 속도를 이유로 컴포넌트에서 axios를 직접 호출하는 경우도 있는데, 이러한 방식은 구조를 무너뜨리기 너무나도 좋은 구조라고 합니다. 나중에 데이터 형식이 바뀌거나 에러 처리를 추가하려고 할 때, 컴포넌트마다 다 수정해야 하기 때문이에요. 컴포넌트는 UI에만 집중하고, 데이터 관련 로직은 훅이나 서비스 계층으로 위임하는 게 좋습니다.

 


마무리하며

API 연동 구조를 잘 나누는 건 결국 "나중에 덜 고생하자"는 목적에 가깝습니다. 프로젝트가 작을 때는 굳이 나눌 필요 없어 보일 수도 있지만, 중장기적으로 유지보수 비용을 줄이고, 협업을 원활하게 해주며, 테스트까지 쉽게 만들어주는 기반이 됩니다.

 

api/, services/, hooks/ 구조는 많은 프론트엔드 팀들이 선택한 검증된 방식이라고 합니다. 이 세 계층이 명확하게 역할을 나누고 있다면, 프로젝트가 커져도 구조는 쉽게 무너지지 않습니다.

 

조금 번거롭더라도 초반부터 구조를 잡아두면, 나중에 코드 리뷰와 리팩터링이 훨씬 수월해질 거에요. 일단 한 번 도입해보고, 팀에 맞게 조정해보는 걸 추천드립니다!