점진적인 리소스 생성 패턴으로 유연한 API 설계하기

이종훈·2025년 3월 21일
2

개발 일지

목록 보기
4/21
post-thumbnail

배경


모바일 청첩장 프로젝트에서 청첩장에 관련된 CRUD API 구현을 담당하였습니다. 이때 기존에 구현한 방식은 기본적으로 청첩장 등록(post)시 모든 column 값들을 req로 보내고, 수정(put)시 수정할 구분의 데이터만 req로 보내는 것으로 구성하였습니다.
하지만 최근 프론트와 의논 결과 post시에는 청첩장의 빈 껍데기값들을 우선적으로 보낸 후, put을 통해 모든 값들을 전달하여 청첩장을 등록하도록 구성을 수정하게 되었습니다. 이는 사용자의 초기 정보가 부족할 때에도 유연하게 사용할 수 있고, 필수 필드 누락 방지와 초기 생성 후 업데이트 등 유지보수하기 좋은 시스템으로서 작용할 수 있게끔 해줍니다.


수정 사항

해당부분을 구현하기 위해선 다음과 같은 수정 사항이 필요합니다.
1. 청첩장 테이블 수정
최초 API 구성에서는 post시 모든 column 값을 전달해야 하므로 청첩장의 필수 필드들의 속성을 not null로 선언했지만, 수정 사항에서는 post시 빈 껍데기 값을 우선적으로 보내기 때문에 not null 속성을 모두 해제해줘야 합니다.
2. put API에 유효성 검사 로직 추가
post로 틀을 구성한 후 put 요청을 통해 모든 필드 값들을 받아오게 됩니다. 이때 put 요청 시 모든 필드 값들이 req에 포함되었는지에 대한 유효성 검사가 필요합니다.


구현

1. 청첩장 테이블 및 model 수정


우선 청첩장 테이블의 기본적인 column들의 not null 속성을 해제합니다.
최초 post시 필요한 필드(id, userId, title)값을 제외한 모든 column들을 default null로 설정합니다.

그 후, 현재 백엔드 코드는 typescript 기반으로 작성되어있기 때문에 기본적인 invitation의 model의 속성을 마찬가지로 수정해줘야 합니다.
src/models/invitations.ts에서 ORM의 sequelize를 기반으로 정의된 allownull 속성을 true로 수정합니다.

2. 유효성 검사 로직 추가

앞서 정리한 것처럼 put 요청 시 청첩장의 필수 필드 값들이 모두 포함되었는지에 대한 유효성 검사가 필요합니다. 현재 API 로직은 router, controller, repository, service layered들을 통해 비즈니스 로직이 구성되어있습니다. 각 계층별 역할은 다음과 같습니다.

  • router: app.ts로부터 전달받은 요청을 controller로 전달
  • controller: 요청을 service에 넘기고 요청을 반환, 이때 반환받은 값에 따라 statuscode를 분기 처리
  • service: 기본적인 비즈니스 로직 처리
  • repository: 데이터베이스 직접 조작

기본적인 비즈니스 로직을 다루는 부분이 service이기 때문에 invitation.service 부분에 유효성 검사 로직을 구현해줘야 합니다.


상단의 코드로 청첩장의 allow null이 true인 모든 필드들에 대한 검사를 수행할 수 있습니다.
각 코드에 대해 자세히 알아보겠습니다.

const attributes = Invitation.getAttributes() as Record<string, any>;

먼저 sequelize로 정의한 Invitation model을 호출하여 해당 column 정보들을 가져와 attribute 변수에 저장합니다. 이때 getAttributes() 메소드를 통해 저장할 수 있는데, typescript는 getAttributes의 반환값이 명확하지 않기 때문에 string, any로 타입 캐스팅을 해줌으로서 타입 에러를 방지할 수 있습니다.

const requiredFields = Object.keys(attributes).filter((field) => attributes[field].allowNull === true);

attributes에 저장된 column 값들을 모두 가져온 후 그 중에서 allownull 속성이 true인 필드들을 걸러내 requiredFields 변수에 저장합니다. 이유는 allownull 속성이 false인 필드들은 청첩장 등록(post)시 필수적으로 등록되어있는 정보이기 때문에 put 요청 시 req 값으로 들어올 필요가 없기 때문입니다.

const missingFields = requiredFields.filter(field => !(field in updatedData));

앞서 정의한 requiredFields 값들 중 req로 전달받은 updatedData에서 빠진 값이 있다면 그 값을 missingFields에 저장합니다.

if(missingFields.length > 0) {
      throw new ValidationError(`청첩장 필수 값이 누락되었습니다: ${missingFields.join(', ')}`);
    };

그 후 missingFields의 값이 존재한다면 빠진 값이 있는 것이기 때문에 ValidationError를 반환합니다.

3. 에러 처리

service에서 필수 필드 값에 대한 유효성 검사를 한 후, 만약 검사에 통과하지 못한다면 error를 반환하여 controller에게 전달해주도록 구현하였습니다. 이때 ValidationError를 따로 정의해줘야 합니다.

유효성 검사 에러를 type class로 선언한 후 모듈화하여 호출하는 방식으로 구성할 수 있습니다.
기본적인 Error 타입을 확장하여 ValidationError라는 클래스를 정의한 후 export 합니다.

그 후 service에서 필수 필드가 빠진 경우 ValidationError를 던집니다.

마지막으로 Controller에서 ValidationError를 전달받은 경우 사용자에게 필드 값이 빠졌다는 메세지를 전송합니다. 이를 통해 유효성 검사를 구현할 수 있습니다.

profile
종훈리의 개발일지

3개의 댓글

comment-user-thumbnail
2025년 3월 23일

포스트그레스큐엘도 좋다던데 한번 사용해보세요~

답글 달기
comment-user-thumbnail
2025년 3월 23일

오 상세히 수정에 대한 내용이 잘 정리 되어있는것 같아요!

답글 달기
comment-user-thumbnail
2025년 3월 24일

글 좀 쓰시네요 👍

답글 달기