AXIOS API 모듈화 깔끔하게 사용하는 방법

2022년 11월 15일

1. 프롤로그

회사에서 프로젝트를 진행하며 불편했던 점을 개선하기 위해 시작했습니다.
평상시에는 아래와 같은 방법으로 사용했지만, 마음에 들지 않아 변경하기로 했습니다.

export const fetchUser = () => backendAPI.get("/user/read");
export const createUser = (userInfo) =>
    backendAPI.post("/user/create", { userInfo });
export const updateUser = (userInfo) =>
    backendAPI.post("/user/update", { userInfo });
export const deleteUser = (userInfo) =>
    backendAPI.post("/user/delete", { userInfo });

2. 탐색해보기

바로 생각나는 코드 스타일을 작성했고, 총 4가지를 작성했습니다.
그리고 오픈 톡방, 지인 개발자분들께 소스를 공유하여 선호하는 코드 번호를 물어봤습니다.

// 1번
export const fetchUser = (userId) =>
    backendAPI.get("/user/read", { params: { userId } });

// 2번
export function fetchUser(userId) {
    return backendAPI({
        method: "get",
        url: "/user/read",
        params: { userId },
    });
}

// 3번
export const fetchUser = (userId) => {
    return backendAPI.get("/user/read", { params: { userId } });
};

// 4번
export const fetchUser = (userId) =>
    backendAPI({
        method: "get",
        url: "/user/read",
        params: { userId },
    });

[투표결과]

  • 1번이 5명
  • 2번이 12명
  • 3번이 2명
  • 4번이 5명

투표 결과를 보고 2번 코드 스타일로 작성하기로 했습니다.
직관적이고 보기 좋다는 분들이 많았기 때문입니다.

3. 작성해보기

투표 결과를 토대로 작성해봤지만, 직관적이지만
중복되는 소스가 너무 많다고 리팩토링을 시작했습니다.

API를 추가할 때마다 export function, return backendAPI
계속 작성해야 해서 손가락이 아파, 해당 부분을 줄이기로 했습니다.

export function fetchUser(userId) {
    return backendAPI({
        method: "get",
        url: "/user/read",
        params: { userId },
    });
}
export function createUser(userInfo) {
    return backendAPI({
        method: "post",
        url: "/user/create",
        data: userInfo,
    });
}
export function updateUser(userInfo) {
    return backendAPI({
        method: "post",
        url: "/user/update",
        data: userInfo,
    });
}
export function deleteUser(userInfo) {
    return backendAPI({
        method: "post",
        url: "/user/delete",
        data: userInfo,
    });
}

4. 1차 고도화

export functionreturn backAPI 를 제외해서 작성하면
오브젝트에 메소드를 추가하는 형식으로 작성할 수 있습니다.

const usersAPI = {
    fetchUser: (userId) => ({
        method: "get",
        url: "/user/read",
        params: { userId },
    }),
    createUser: (userInfo) => ({
        method: "post",
        url: "/user/create",
        data: userInfo,
    }),
    updateUser: (userInfo) => ({
        method: "post",
        url: "/user/update",
        data: userInfo,
    }),
    deleteUser: (userInfo) => ({
        method: "post",
        url: "/user/delete",
        data: userInfo,
    }),
};

그리고 기존에 있던 유틸리티 함수를 하나 만들어서
이전처럼 동작하게 구현하면 됩니다.

export function addInstace(instance, instanceObjectAPI) {
    const instanceMap = ([key, callback]) => [
        key,
        (...parameters) => instance(callback(...parameters)),
    ];
    return Object.fromEntries(
        Object.entries(instanceObjectAPI).map(instanceMap)
    );
}

const usersAPI = addInstance(backendAPI, {
    fetchUser: (userId) => ({
        method: "get",
        url: "/user/read",
        params: { userId },
    }),
    createUser: (userInfo) => ({
        method: "post",
        url: "/user/create",
        data: userInfo,
    }),
    updateUser: (userInfo) => ({
        method: "post",
        url: "/user/update",
        data: userInfo,
    }),
    deleteUser: (userInfo) => ({
        method: "post",
        url: "/user/delete",
        data: userInfo,
    }),
});
export const {
    fetchUser, // 조회
    createUser, // 생성
    updateUser, // 수정
    deleteUser, // 삭제
} = usersAPI;

이전에 비해 직관적이며, 보기 좋아졌지만
그룹화와 Method 방식을 분류시키는 함수도 작성하기로 했습니다.

5. 2차 고도화

  • addInstaceGroup : 그룹화
  • addInstaceMethod : Method 분류
  • addInstaceGroupMethod : 그룹화와 Method 분류
export function addInstaceGroup(instance, instanceObjectAPI) {
    return Object.fromEntries(
        Object.entries(instanceObjectAPI).map(([key, value]) => [
            key,
            addInstace(instance, value),
        ])
    );
}

export function addInstaceMethod(instance, instanceObjectAPI) {
    return Object.fromEntries(
        Object.entries(instanceObjectAPI)
            .filter(([method]) =>
                ["get", "post", "put", "delete"].includes(
                    method.toLocaleLowerCase()
                )
            )
            .map(([method, value]) => {
                const instanceMap = ([key, callback]) => [
                    key,
                    (...parameters) =>
                        instance({ ...callback(...parameters), method }),
                ];
                return [
                    method,
                    Object.fromEntries(Object.entries(value).map(instanceMap)),
                ];
            })
    );
}

export function addInstaceGroupMethod(instance, instanceObjectAPI) {
    return Object.fromEntries(
        Object.entries(instanceObjectAPI).map(([key, value]) => [
            key,
            addInstaceMethod(instance, value),
        ])
    );
}

5.1 Group

그룹화를 추가하면 좀 더 객체 구조로 직관적으로 볼 수 있습니다.

const backendAPIGroup = addInstaceGroup(backendAPI, {
    users: {
        fetchUser: () => ({
            method: "get",
            url: "/user/read",
        }),
        createUser: (userInfo) => ({
            method: "post",
            url: "/user/create",
            data: userInfo,
        }),
        updateUser: (userInfo) => ({
            method: "post",
            url: "/user/update",
            data: userInfo,
        }),
        deleteUser: (userInfo) => ({
            method: "post",
            url: "/user/delete",
            data: userInfo,
        }),
    },
});
export const {
    users: {
        fetchUser, // 유저 조회
        createUser, // 유저 생성
        updateUser, // 유저 수정
        deleteUser, // 유저 삭제
    },
} = backendAPIGroup;

5.2 Method

Method를 사용하면 get post put delete 4가지로 분류해서 확인할 수 있습니다.
처음 보는 사람입장에서는 객체 구조 만 봐도 호출하는 API method 방식을 이해할 수 있습니다.

const backendAPIMethod = addInstaceMethod(backendAPI, {
    get: {
        fetchUser: () => ({
            url: "/user/read",
        }),
    },
    post: {
        createUser: (userInfo) => ({
            url: "/user/create",
            data: userInfo,
        }),
        updateUser: (userInfo) => ({
            url: "/user/update",
            data: userInfo,
        }),
        deleteUser: (userInfo) => ({
            url: "/user/delete",
            data: userInfo,
        }),
    },
});
export const {
    get: {
        fetchUser, // 유저 조회
    },
    post: {
        createUser, // 유저 생성
        updateUser, // 유저 수정
        deleteUser, // 유저 삭제
    },
} = backendAPIMethod;

5.3 Group Method

Group의 Method를 합친 방법입니다.
소스의 네이밍에 따라 소스의 범위와 사용하는 Method 방식을 바로 파악할 수 있습니다.

const backendAPIGroupMethod = addInstaceGroupMethod(backendAPI, {
    users: {
        get: {
            fetchUser: () => ({
                url: "/user/read",
            }),
        },
        post: {
            createUser: (userInfo) => ({
                url: "/user/create",
                data: userInfo,
            }),
            updateUser: (userInfo) => ({
                url: "/user/update",
                data: userInfo,
            }),
            deleteUser: (userInfo) => ({
                url: "/user/delete",
                data: userInfo,
            }),
        },
    },
});
export const {
    users: {
        get: {
            fetchUser, // 유저 조회
        },
        post: {
            createUser, // 유저 생성
            updateUser, // 유저 수정
            deleteUser, // 유저 삭제
        },
    },
} = backendAPIGroupMethod;

정리

오늘 작성한 코드는 만족스러웠지만
다음에 좀 더 공부해서 좋은 코드를 작성하고 싶다.

github icon

Copyright 2022. Minseok0917. All rights reserved.