SMAIVNN
article thumbnail

현재 혼자 진행중인 프로젝트는 Next.js + Nest.js 조합으로 진행중입니다.

 

프로젝트를 진행하며 백엔드 로그인 코드를 조금 수정하였는데, 기존에 잘 작동하던 프론트 부분 코드가 작동하지 않는 상황이 발생하였습니다.

 

분명 로직은 그대로이고 (오히려 단순화 되었고) 변한 부분이 크지 않은데 왜 갑자기 안되지 싶었는데, 원인은 아래 코드였습니다.

 

해당 코드의 const result = await res.json() 부분이 문제였습니다.

그냥 return받은 response의 json객체를 사용하는 것인데 왜? 라고 생각하실 수 있는데, 문제는 백엔드 로직이 json객체를 더 이상 반환하지 않게 수정 되었다는 것입니다.

 

기존에는 다양한 정보와 토큰을 주었지만 로직이 변경되며 정보 객체를 반환하지 않도록 수정하였습니다. 따라서 백엔드 부분에서는 단순히 처리만 진행하면 되고 반환하는 값이 없었던 것입니다.

 

어떻게 return 해야 할까?

저의 경우, 굳이 response에 정보를 담지 않아도 되는 상태입니다. 하지만 에러가 발생할 경우 json객체가 있음은 분명하고, 그럼 json객체 확인 로직을 추가할까? 모든 api에 {success} 값을 추가할까? 라는 생각도 해보았습니다. 하지만 굳이요..? REST API에는 이미 이를 위한 방법들이 존재합니다!

 

 

중요한 부분

저는 API의 response에서는 다음 세 가지 항목이 중요하다고 판단하였습니다.

1. API는 일관성이 있어야합니다.

2. 정확한 정보를 담아야 합니다.

3. 개발의 측면에서 클라이언트, 서버가 응답 형식에 의존하지 않고 각자가 구현 가능해야합니다.

 

조금더 자세히 각 내용에 대해 이야기 해보겠습니다.

 

API는 일관성이 있어야 한다.

우리가 요청 응답에 대해서 요구하는 것은 명확합니다. 바로 활용입니다. 

데이터를 활용하려면 예측 가능해야합니다. 즉, 정해진 응답 구조가 있어야 합니다. 

 

모든 응답은 아래와 같이 일관된 구조를 가져야 합니다. 예를들어, 성공 응답과 에러 응답 모두가 아래 양식을 지녔음을 안다면 API를 활용하는 입장에서 학습 곡선도 단축되고 유지 보수도 용이하다는 장점이 있습니다.

{
  "status": "success",
  "data": { ... }
}

{
  "status": "error",
  "message": "Detailed error message"
}

 

정확한 정보를 담아야 한다.

아노드 로렛의 책 " 웹 API 디자인 " 에서는 다음과 같이 말합니다.


"API의 성공 피드백은 컨슈머가 단순히 리퀘스트가 전달되었음을 인지하는 것 이상의 정보를 제공해줘야 합니다. REST API 스타일을 사용할 때에는 유용한 성공 피드백을 제공하는 것은 에러 피드백을 만들어 내는 것과 동일한 것에 달려 있습니다. 정확한 HTTP 상태 코드와 직관적인 리스폰스 바디입니다."


성공 피드백은 무슨 일이 벌어졌는지와 그 다음에 무엇을 해야하는지에 대한 정보를 구체적으로 제공해주는 것이 좋습니다. 이는 활용하는 입장에서의 효율성을 증가시킵니다. 리스폰스 바디 뿐만이 아닌 정확한 상태 코드를 활용하는 것 또한 직관적인 이해를 돕는 역할을 합니다.

 

응답 형식에 의존하지 않아야 한다.

결국 위에 두 가지 조건이 달성되었을 때, 개발자는 "문서만으로 대화하기"가 가능해집니다. 어떤 형태의 리스폰스가 올지 예측 가능하고 어떻게 활용할지를 알기 때문입니다.

 

프론트, 백 개발자가 각자의 역할에서 정해진 규칙속에 구현을 진행하면 서로가 어떻게 구현하였는지를 알 필요 없이 본인의 역할만을 진행하면 되는 것입니다.

 

해결

저는 앞의 규칙을 토대로 상황을 해결하였습니다. 정확한 규칙을 세우고 일관성을 적용하는 것입니다.

 

Status code를 적극적으로 활용한다.

status code는 약속입니다. 사실 앞선 문제 상황을 해결하는 방법은 단순합니다. 리스폰스 바디 항목이 없기에 '204 No Content' 약속을 사용하여 반환하면 됩니다. 프론트에서는 'No Content'이기에 응답 본문이 없다는 것을 알고 이 부분만 분기처리를 진행하면 됩니다. 약속에 의거한 단순한 해결 방법입니다. 굳이 모든 response에 빈 객체 {} 혹은 success: true를 넣어주지 않아도 됩니다.

 

 

if문이 내부에서 return을 하고 그 이후 res.json()을 사용하는 것은 if문이 끝난 이후에 진행하는 것입니다. 정석적이고 당연하고 쉬운 방법입니다.

 

규칙있는 API를 만들자

지금 당장 부족한 부분입니다. 에러처리에 관한 부분은 이전에 작성한 게시물(에러처리 더 잘하기) 부분을 보면 알 수 있듯이 Exeption filter를 사용하고 있습니다. 하지만 저는 성공적 응답에 대해서 깊게 생각한 적은 없습니다.

 

앞선 예시의 양식과 같이 성공 응답에 대해서도 양식에 맞는 response를 준다면 더욱 좋은 API가 될 것입니다. 아직 적용 적인지만 아래와 같은 인터셉터를 만들어볼 수 있을 것 같습니다.

import {
  CallHandler,
  ExecutionContext,
  Injectable,
  NestInterceptor,
} from '@nestjs/common';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';

@Injectable()
export class ResponseFormatInterceptor implements NestInterceptor {
  intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
    return next.handle().pipe(
      map((data) => ({
        status: 'success',
        data,
      })),
    );
  }
}

 


 

결론적으로, 시작은 별거 아닌 부분이였습니다. 글을 읽는 여러분도 왜 당연한 얘기를 하지?라고 생각할 수 있습니다.

프로젝트, 그리고 협업을 진행하며 당연히 정하고 가야하는 규칙들이지만 아직 프로젝트 경험이 학생 단위의 협업 경험만 있는 저로서는 그동안 크게 신경쓰지 못한 부분입니다. 

 

이런 작은 습관이 하나하나 쌓여 더 좋은 코드, 더 좋은 협업을 이루어 낸다고 생각합니다.

 

작은 문제점에서 앞으로 항상 적용 할 성장을 얻었다는 점에서 기쁩니다.

profile

SMAIVNN

@SMAIVNN

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!