약 한 달 전쯤부터 이 글의 내용을 구상하고 있었는데, 얼마 전에 소속된 커뮤니티에서 우연히 오늘 주제에 관한 얘기가 나와 속으로 굉장히 신기해했던 경험이 있다. 생각보다 많은 사람들이 관심을 보이는 주제였는데, 나와 비슷한 생각을 하고 있음에 굉장히 반가웠다.
이제부터 API Interface를 구성할 예정이다. 사실 오늘 구성하려는 구조에 대한 명칭이 따로 있는지는 모르겠다. API Interface라는 명칭은 내가 용어정리를 위해 붙인 이름인데, 잘못된 명칭이라면 알려주시면 감사하겠습니다. 개인적인 경험을 통한 방법을 제시하는 것이니, 꼭 정답은 아니면 더 좋은 방법이 있을 경우 제보 주시면 반영해보도록 할게요 ^_^; (부담 부담)
API를 호출하는 URL 설정값은 프로젝트 구조에 따라 조금씩 다를 텐데, 보통은 config directory나 env 파일로 설정되어 있을 것이다. 우리는 우선 이 설정된 URL과 조합하여 API url list를 만들 것이다. 준비물로 swagger UI등의 API 목록이 필요하다.
단, url 리스트는 api.js를 좀 더 편하게 만들기 위한 과정이라서, 실무에서 API 변동이 많거나(API 바뀌는 건 실제로는 지양해야 할 일) API list가 많지 않아 관리가 쉽다면 작성하지 않아도 무관하다. 예시로 든 경로는 사용자의 편의에 따라 얼마든지 변경 가능하다.
export const API_URL = process.env.BASE_URL // 추후 api list에서 API URL을 쉽게 불러오기 위한 용도
// 혹은 export const API_URL = "https://test.com/api/v1/"
// 이렇게 직접 넣는 경우도 있을 것이다.
export const API = {
users: 'users', // API list의 구조를 나열해주세요.
user: (args) => `users/${args.userId}`
...
}
export const ETC_API = {
tests: 'tests',
test: (args) => `users/${args.args1}/depth1/${args.args2}/depth2/${args.args3}`
...
}
- API_URL은 추후 api list에서 API URL을 쉽게 불러올 용도로 만들어뒀다. 구조에 따라 필요 없으면 생략은 가능할 것이다.
- const API와 ETC_API 같은 경우는 주로 swagger controller 별로 구분하는 것이 좋은듯하다. 하지만 양이 많지 않은 경우에 구분하지 않아도 무관하다.
- 각 상수 안에는 parameter 값을 제외한 API list를 나열한다. 이 경우에는 API 호출 시 경로 안에 들어가는 값들을 정리하기가 손쉬워진다.
- 전역에 있는 값을 반복적으로 넣어주는 구조가 있을 경우, 이 단계에서 참조해도 무관하다. (ex: 사용자 정보..)
- 다른 메소드로 동일한 경로가 있을 경우에는 한 번만 적어준다.
axios 호출에 관한 Interface를 만드는 파일이다. 해당 코드는 axios를 직접 설정하는 기준에 맞춰져 있어서, vue-axios나 nuxt/axios를 사용하는 경우에는 사용법이 달라질 수 있다.
import axios from 'axios'
// 프로젝트 설정에 맞게, 기본적인 정보를 넣어주세요
const service = axios.create({
baseURL,
timeout,
withCredentials,
...
})
// axios 요청 시 필요한 정보를 넣어주세요
service.interceptors.request.use(
(config) => {
config.headers = {}
}
)
// 응답에 필요한 처리를 넣어주세요.
service.interceptors.response.use(
(res) => {},
(error) => Promise.reject(error)
)
// 각 메소드별 함수를 생성해 주세요.
export default {
async get(...options) {
try {
const res = await service.get(...options)
return res
} catch (e) {
return printError(e)
}
},
async post(...options) {
// 공통
},
async put(...options) {
// 공통
},
async delete(...options) {
// 공통
},
}
참조: axios.create, axios.interceptors
- 구조에 따라, create 안에 baseURL이 들어갈 수도 있다.
- service.js는 api 호출할 때 공통적으로 발생하는 처리들을 담당한다.
- request에는 주로 header와 token 관련 설정들을 처리해 준다.
- reponse에서도 token 처리나 응답에 대한 공통 로직을 처리해 준다.
- get, post, put, delete는 각각 API 호출 시에 직접 호출될 함수들이다. API 호출 시의 option을 전달하며, 공통적인 에러 처리를 한다.
- 메소드별 공통 로직은 경우에 따라 함수로 분리할 수 있다.
대망의 마지막 작업이다. 이제 이렇게 만들어진 service와 url list를 묶어 진짜 호출되는 api 함수를 만드는 작업이다.
import service from './service
import { API_URL, API, ETC_API } from './config'
export const api = {
getUser() {
return service.get(`${API_URL}${API.users}`)
},
setUser(args) {
return service.post(`${API_URL}${API.user(args)}`)
},
updateUser(args, param) { // args와 param이 동시 존재하는 경우
return service.put(`${API_URL}${API.user(args)}`, param)
}
}
export const testApi = {
getTests() {
return service.get(`${API_URL}${API.tests}`)
},
/* test 등록하기
* @param { Object } args
* @param { string } args.args1 - 이것
* @param { string } args.args2 - 저것
* @param { string } args.args3 - 그것
*/
setTest(args) {
return service.post(`${API_URL}${API.test(args)}`)
}
}
- 함수 이름은 메소드와 관련한 값을 붙혀주는 것이 좋다. ex) user 정보일 때, getUser
- 메소드별 어울리는 이름 (get: get, post: set, put: update, delete: delete - 축약 가능)
- 메소드 다음은 보통 swagger에 있는 이름을 그대로 사용해 주면 된다.
- 함수 위에는 js doc을 이용하여, parameter 정보를 표시해 주는 것이 좋다.
자 이제 interface는 다 만들었다. 우선은 각 메소드 별로 복잡도가 있는 API를 하나씩 골라, 코드에 직접 적용해서 테스트해보고 호출에 성공하면 적용 가능하다. 그러면 어떻게 적용을 해볼 것인가?
import { api } from '@/api/api'
methods: {
async init() {
await api.getUser().then(res => {
this.setUser(res)
})
}
}
자 얼마나 편한가? 그런데 API 호출이 너무 많아서 적용할 때 검증이 어려워서 고민일 수 있다. 각 메소드별 검증은 끝난 후니, 페이지 수정이 있을 때마다 작업을 하면서 점진적으로 적용해나가면 된다. API interface는 서버 환경과 같은 문제를 제외하고는 동일한 동작을 하므로, 오타나 잘못된 파라미터가 아닌 이상 문제가 생길 여지는 적다. 그 외의 문제는 interface 자체의 설정을 잘못 넣은 케이스니 수정이 필요하다.
좋은 점 하나 더! 짜잔 API 목록의 자동완성이 가능하다는 것이다. 우리는 swagger UI와 이름을 맞춰서 작성했기 때문에, 쉽게 호출할 API를 찾을 수 있다.
메소드 자동완성은 더 좋다.열심히 설명해보려고 했으나, 사람들이 쉽게 이해할 수 있을지 모르겠다. ㅠ_ㅠ
이번에는 API를 수정했으니, 다음에는 컴포넌트 내의 코드에 손을 대야 하지 않을까? 다음 글은 컴포넌트 내의 method에 관한 내용이다.