개발자를 꿈꾸는 사람이라면 한번씩 들어보거나 고민하게되는 질문이 있습니다.
"넌 프론트 엔드로 가고 싶어 백엔드로 가고 싶어?"
물론 개발자는 이 둘만 있는게 아니라 다양하지만, 대체로 그 질문을 받게 됩니다. 둘 중 무엇을 선택하든 혼자서 다 하는 것이 아니라면 분업을 하게 될테고 그렇게 되면, 다른 한쪽에 대해 몰라서, 여러 실수가 나게 됩니다. 그래서 이번 게시물에서는 Vi.NO 프로젝트를 진행하면서 생긴 사례를 통해 어떤 것을 주의하면 될지 말씀 드리겠습니다! 아마 당연한 것들이라 이런것을 실수한다고 생각하는 분들도 있겠고, 아, 이래야하구나 생각하는 분들도 있을 것 같습니다.
API를 설계하게 되면 백엔드 개발자의 입장에서 쉽지만 어려운.. 그 단계가 EndPoint 경로를 어떤식으로 설계할까입니다. 초반에 다 설계한다면, 어려움이 줄어들겠지만, 프로젝트 중간중간 새로운 API를 설계해야한다면, 아예 다른식으로 짜자니, 뭔가 애매하고, 기존것을 바꾸자니, 프론트 쪽에서 개발하는게 다 엉킬것같고, 많이 고민되기도 합니다.
필자는 이런 실수를 많이 했습니다.
💬 프론트: "이미 get,post등의 명칭이 있는데, 엔드포인트 뒤에 /get이나 post를 쓰시는건가요?"
맞습니다.. 엔드포인트를 설계할때, 뭔가 충돌 나는 부분도 있어서, 기존 것을 고치기 보다는, 뒤에 /get, /post등을 붙이면 되겠지! 라는 안일한 생각을 해 설계를 했지만 오히려 그것이, 프론트 쪽한테 혼란을 준것 같았습니다. 그래서 API 엔드포인트를 설계하는 좋은 방법들을 추가로 알려드리겠습니다.
왜 동사를 쓰면 안되나면
HTTP 메소드들이 이미 GET, PUT, PATCH, DELETE라는 동작을 동사의 형태로 나타내기 때문입니다.
GET, POST, PUT, PATCH, DELETE는 가장 주로 쓰이는 HTTP 동사들이기 때문에 굳이 동사를 쓸필요가 없이 명사로 나타내는 것이 가장 좋습니다. 예를 들면
/{userId}/getPosts 보다는
/{userId}/posts 로 작성하는 것이 좋습니다.
어차피 get, post 등으로 동사인것을 표시해주기 때문이죠!!
만약 /post/{postId}라는 엔드포인트가 있다고 해봅시다. get,delete,post 등으로 사용이 가능할것인데, 사용자는 저 postId만 보고는 저 id가 다른 id로도 되는지 햇갈릴 것입니다. 그래서
/post/{postId}보다는 /posts/{postId}로 접근하는 것이 좋습니다
사실 api를 설계하면서 값이 없는 경우 등은 오류로 취급하기도 합니다. id조회를 했는데, id가 없을경우 빈값보다는 에러를 보내줘, 해당 id가 없다는 것을 알리기도 하죠. 하지만 검색 같은 기능을 구현할떄는 오히려 그것 보다는 빈 배열을 주는 것이 프론트 엔드 개발자 입장에서는 더 편리할 수도 있습니다. 다음과 같은 과정이 있다 가정합시다.
검색 API중 검색 결과가 없으면 error를 호출한다
- 프론트에서 error를 받으면 에러 코드를 확인한다.
- 값이 없다는 에러코드를 받으면 빈 값을 보여준다.
물론 이 과정이 정석이기는 합니다. 하지만 에러 핸들링하는 과정이 복잡하기도 하고, 빈값을 주는 것이 오류라고 생각하기엔 애매하니까요. 그래서 보통 프론트엔드 개발자분들과 이야기하면서 조율하긴 하지만, 검색 API에서 결과가 없을 경우 빈값을 주는것이 좋습니다. 그 경우 다음 과정을 보입니다.
1.검색 API 중 검색 결과가 없으면 빈 배열을 리턴한다.
2.프론트에서는 빈 배열을 받았지만, 오류는 아니므로 그대로 화면에 보여준다.
조금 더 과정이 간결해지는 거죠. 물론 중요한건 소통입니다.
API를 설계하게 되면, 보통 PostMan 등으로 테스트를 해보게 됩니다. API 종류에 따라 파라미터를 주기도 하고, 바디를 API에다가 넘기기도 하죠. 그래서 흔히 발생하는 실수 일수도 있습니다. Axios를 사용할때 주의점! 바로 그건
Axios의 get 메소드에서는 request body가 없습니다.
Axios에서 제공되는 가이드라인을 보면
axios.get(url[, config]) // GET
axios.post(url[, data[, config]]) // POST
axios.put(url[, data[, config]]) // PUT
axios.patch(url[, data[, config]]) // PATCH
axios.delete(url[, config]) // DELETE
axios.request(config)
axios.head(url[, config])
axios.options(url[, config])
post,put,patch는 "data" 를 포함하고 있지만, get은 포함하지 않습니다. 그렇기에 애초에 포함할수가 없다는 거죠.
사실 생각만 해보면, get 메소드를 호출할때는 간단한 정보만 포함되므로, 굳이 body안에 담을 필요는 없는 것을 알 수 있습니다.
당연한 소리입니다만 API 설계를 혼자 하는 것이 아닌 분업을 하다보면, 무심코 형식이 통일이 되지 않을때가 있습니다. 그렇기 위해서 개발을 시작하기전 규칙을 먼저 정하고 하지만, 코드를 작성하다보면 무심코, 잘못 짤 수 도 있죠. 저희 Vi.NO에서 정한 규칙은
변수 명, 엔드포인트 등지에서는 camelCase를 쓴다.
response 데이터, reqest body 데이터는 snake_case를 사용한다.
는 것입니다. 간단하게는 작성해놨지만, 생각보다 중요합니다. 매번 형식이 달라진다면 프론트 분들 입장에서는 저 API에서 주는 데이터가 어떤 변수 형식으로 되어있는지 햇갈리거든요. 그래서 꼭 지켜줘야 합니다.
이것 또한 당연한 소리로 들릴지 모르겠습니다. API를 테스트할때는 성공 인경우, 실패인 경우 등을 기본으로 테스트해야합니다. 그러기 위해서 테스트 케이스를 설계하는 것이 중요합니다.
예를 들면, 한 API에 존재하는 값을 엔드포인트로 보냈을때 원하는 값만 받아왔다고 배포를 해버리면, 나중에 버그가 발생했을때엔, 프론트 분들이 또 혼란에 빠질 수도 있거든요. 그렇기에 지속적인 테스트를 해서 안정적으로 배포해야 합니다.
이번 포스트에서는 백엔드 개발자로써, 프론트 개발자와 협업해야할때 주의해야할점을 알려드렸습니다. 당연한 소리일 수도 있긴 하지만, 놓치기 쉬운 포인트 일수도 있습니다. 물론 이 내용을 통틀어서 제일 중요한 것은 바로 소통이지만요. 모두 도움이 되었으면 좋겠습니다!