본문 바로가기
카테캠/2단계

[7/18] Fetch API Wrapping

by 쪼꼬에몽 2025. 7. 18.

필요한 이유

1. 반복 코드 제거

fetch(url, {
	method: 'POST',
    headers: {
    	'Content-Type': 'application/json'
    },
    body: JSON,stringify(data)
});

- 매번 이렇게 fetch()를 작성하면 중복 코드가 많고, 실수 가능성도 증가 

2. 변경에 강한 코드 구조

- 나중에 fetch -> aixos 또는 axios -> bxios 등으로 변경하게 되면, 전역적으로 fetch가 쓰인 모든 곳을 수정해야 함

- 레퍼를 이용해 중앙 레퍼만 바꾸면 전체 API 호출 방식이 바뀜

3. 공통 처리 (관심사 분리)

- 인증 토큰 삽입, 오류 처리, 응답 파싱 등을 중앙에서 한 번만 처리 가능

- 로그인한 유저라면 요청마다 Authorization 헤더 붙여야 함 

4. 로깅, 캐시, 리트라이 정책, 인터셉터 등

실무에서의 공통 로직을 래핑하면 쉽게 추가 가능

- 로딩 인디케이터 처리

- 에러 로깅

- 요청 캐싱

- 실패 시 재시도

fetch 기반 래퍼 ApiClient

export default class ApiClient {
	static async get(endpoint, headers = {}) {
    	return this.request('GET', endpoint, null, headers);
    }
    static async post(endpoint, body, headers = {}) {
    	return this.request('POST', endpoint, body, headers);
    }
    static async put(endpoint, body, headers = {}) {
    	return this.request('PUT', endpoint, body, headers);
    }
    static async delete(endpoint, headers = {}) {
    	return this.request('DELETE', endpoint, null, headers);
    }
	...

- get, post, put, delete는 각 HTTP method에 맞는 요청을 만들어주는 헬퍼

- 실질적 호출은 request()가 담당

- 모든 API 호출을 답당하는 정적 유틸 클래스

- static 메서드만 사용하므로 인스턴스 생성 불필요

- newApiClient() 말고 ApiClient.get(...)처럼 사용 가능 

static async request(method, endpoint, body = null, headers = {}) {
	const url = `${BASE_URL}${endpoint}`;
    const options = {
    	method,
        headers: {
        	'Content-Type': 'application/json',
            ...headers
        },
        body: body ? JSON.stringify(body) : null
    };
    
    try {
    	const response = await fetch(url, options);
        const data = await response.json();
        if (!response.ok) {
        	throw new Error(data.message || 'error occured');
        }
        return data;
    } catch (error) {
    	throw error;
    }
}

- BASE_URL을 기준으로 모든 요청 URL을 구성함

- 공통 headers 처리 (Content-Type, 인증 등)

- 필요시 추가 headers 병합 

- 응답 오류 처리 (response.ok 체크)

- 에러 발생 시 메시지를 추출해 예외 throw 

사용 예시

const user = await ApiClient.get('/users/123');
const created = await ApiClient.post('/users', { name: 'John' });
const updated = await ApiClient.put('/users/123', { name: 'Jane' });
await ApiClient.delete('/users/123');

 

axios 기반 래퍼 initInstance

const initInstance = (config: CreateAxiosDefaults): AxiosInstance => {
	const instance = axios.create({
    	timeout: 5000,
        headers: {
        	'Content-Type': 'application/json',
            ...config.headers,
        },
        ...config,
    });
    
    instance.interceptors.request.use(config => {
    	const authToken = userInfoStorage.get()?.authToken;
        if(authToken) {
        	config.headers.Authorization = authToken;
        }
        return config;
    });
    
    return instance;
};

- axios.create()를 통해 커스텀 인스턴스를 생성

- 기본 timeout, 공통 headers 설정 

- 인터셉터(request interceptor)를 통해 자동으로 Authorization 헤더 삽입

- 인스턴스만 사용하면 항상 인증 토큰 붙어 있음

- 응답 인터셉터도 추가 가능 

axios.create()

- 새로운 axios 인스턴스를 생성함

 

interceptors.request.use()

- 요청이 서버에 보내지기 전에 요청 설정을 가로채는 기능 

- 토큰 자동 삽입

 

CreateAxiosDefaults

- axios 기본 설정 타입 (baseURL, headers 등)

 

AxiosInstance

- 커스터마이즈된 axios 객체의 타입 

const initInstance = (config: CreateAxiosDefaults): AxiosInstance => {...}

- config : 호출 시 전달되는 사용자 정의 설정

- 반환값은 AxiosInstance : 우리가 만든 설정을 적용한 axios 객체 

const instance = axios.create({
	timeout: 5000,
    headers: {
    	'Content-Type': 'application/json',
        ...config.headers,
    },
    ...config,
});

- timeout: 5000 : 5초 내 응답이 없으면 요청 실패

- headers : 기본적으로 JSON 타입의 본문 사용

- ...config.headers : 외부에서 전달한 헤더 설정 병합

- ...config : 그 외 전달된 설정도 포함 (baseURL, withCredentials 등)

- ...config는 마지막에 두어야 외부 설정이 우선됨

instance.interceptors.request.use(config => {
	const authToken = userInfoStorage.get()?.authToken;
    if (authToken) {
    	config.headers.Authorization = authToken;
    }
    return config;
});

- 요청이 서버로 전송되기 직전에 이 함수가 실행됨

- authToken을 꺼내서 요청 헤더에 자동으로 붙임 

const userInfoStorage = {
	get: () => JSON.parse(localStorage.getItem('userInfo') || '{}'),
    set: data => localStorage.setItem('userInfo', JSON.stringify(data))
}

사용 예시

export const apiInstance = initInstance({
	baseURL: url,
});

const user = await apiInstance.get('/users/me');
const updated = await apiInstance.post('/posts', { title: 'Hello' });

 

 

'카테캠 > 2단계' 카테고리의 다른 글

[7/22] 무한 스크롤 옵저버  (0) 2025.07.22
[7/21] Intersection Observer  (1) 2025.07.22
[7/17] local storage  (0) 2025.07.18
[7/17] 로그인 axios post  (3) 2025.07.18
[7/16] Axios  (4) 2025.07.16