Programming/Web

REST API Response Format, 응답 객체는 어떤 형식이 좋을까?

Jan92 2023. 2. 25. 14:05

rest api response format and example

rest api response

 

1. REST API 응답 형식에 대한 의문

지금까지 백엔드로 RESTful API를 여러 번 작업하면서 의문을 느낀 점은 각각의 프로젝트마다 'api 요청에 대한 response 형식이 다르다'는 것입니다.

 

이유는 RESTful API 응답 형식에 대한 표준이 없기 때문인데요.

표준은 없더라도 많이 사용되거나 선호되는 형식이 있을 것 같았으며, 해당 형식을 사용하는 합당한 이유들이 존재할 것 같아서 그에 대한 내용을 찾아보았습니다.

(rest api response 형식에 대해서는 stackoverflow 등의 커뮤니티에서도 활발한 토론이 이뤄지고 있습니다.)

 

추가로 내용을 찾아보면서 개인적으로 적합하다고 생각되는 응답 형식의 예시와 해당 형식을 반환하는 코드를 spring boot에서 구현해 보았으며, 코드는 포스팅 맨 하단 github 링크를 통해 참고할 수 있습니다.

 

 


2. RESTful API의 Response는 어떤 형식으로 사용되는가?

앞에서 말한 것처럼 restful api 응답 형식에는 표준이 존재하지 않습니다.

구글링을 통해 관련 내용을 찾아본 결과, 표준은 존재하지 않지만 response 형식은 크게 아래 2가지 형태로 사용되는 것을 확인할 수 있었는데요.

 

 

2-1. HTTP Status Code + JSON Body를 사용하는 방식

 

성공과 오류 응답 모두 http status code와 json body를 함께 사용합니다.

기존 http 상태 코드라는 표준을 기반으로 응답 데이터를 추가한 것이기 때문에 어느 정도 표준의 영역에 있다고 볼 수 있습니다.

구현 방식에 따라 다르겠지만 성공과 오류에 대한 json body 형식이 다를 수 있습니다.

 

    //GET 요청으로 단일 데이터를 가져올 때
    HTTP/1.1 200
    Content-Type: application/json

    {
        "id": 11,
        "title": "Effective Java",
        "contents": "Java Platform Best Practices Guide"
    }

    //POST 요청으로 데이터를 생성한 결과
    HTTP/1.1  201
    Location: /v1/book/11
    Content-Type: application/json
 
    {
      "message": "The Book was created successfully"
    }

    //GET 요청으로 단일 데이터를 가져오는데 실패했을 때
    HTTP/1.1  404
    Content-Type: application/json
 
    {
      "message": "The item does not exist"
    }

    //잘못된 인증 값을 가지고 요청했을 때
    HTTP/1.1  401
    Content-Type: application/json
 
    {
      "message": "Authentication credentials were missing or incorrect"
    }

- 출처: https://github.com/cryptlex/rest-api-response-format

 

 

 

2-2. JSON Body만 사용하는 방식 (HTTP Status Code는 항상 200)

 

두 번째 방식은 HTTP 상태 코드를 항상 200으로 응답하며, json body 만을 통해 성공 또는 오류 응답을 판단하고, 해당되는 데이터를 함께 반환하는 방식입니다.

이 방식의 경우 완전히 비표준이라는 점이 특징인데요. 'JSend'라고 하는 응답 형식이 해당 방식을 기반으로 하는 잘 알려진 rest api json response 사양입니다.

 

    //GET 요청으로 단일 데이터를 가져올 때
    {
        "status" : "success",
        "data" : {
                "id" : 1,
                "title" : "Another blog post"
        }
    }

    //일반적인 에러가 발생했을 때
    {
        "status" : "fail",
        "message" : "fail message"
    }

    //필드 에러가 발생했을 때
    {
        "status" : "fail",
        "message" : "fail message",
        "errors" : [{error1}, {error2} ... ]
    }

    //Exception이 발생했을 때
    {
        "status" : "error",
        "message" : "custom message"
    }

- 출처: https://github.com/omniti-labs/jsend

 

(http 상태 코드만을 사용하는 방식도 있으나, 해당 방식은 많은 데이터가 오가야 하는 현재 환경에 적합하지 않다고 생각하여 제외하였습니다.)

 

 


3. 개인적으로 선호하는 방식

(여기서부터는 개인적인 의견입니다. 정답은 아니기 때문에 생각하시는 것과 다를 수 있다는 점을 고려해 주시면 좋을 것 같습니다.)

 

//json body만 사용하는 방식이 선호되는 이유
1. HTTP Status Code는 브라우저를 위해 설계되었습니다.
2. HTTP 공식 사양에는 41개 이상의 상태 코드가 존재하며, 모든 코드에 대해 각각의 응답을 처리하기에는 경우의 수가 너무 많습니다.

개인적으로는 http 상태 코드를 제외한 json body만을 사용하는 방식이 선호되었는데요.

 

해당 방식이 좋다고 생각하는 이유는 HTTP Status Code는 애초에 브라우저를 위해 설계된 것으로, restful api는 대부분 브라우저가 아닌 특정한 클라이언트를 위한 것이기 때문에 브라우저를 위한 응답 값보다 더 구체적인 내용들이 필요하다는 것입니다.

 

또한 http 공식 사양에는 41개 이상의 상태 코드가 존재하는데요. 이 상태 코드 전체를 사용하여 응답을 내려주고 처리하기에는 상태 코드가 너무 많으며, 또 각각의 상태 코드에 대한 여러 해석이 있을 수도 있기 때문입니다.

 

때문에 json body만을 사용하는 JSend 사양을 참고하여 api response를 만들어보았고, 내용은 아래와 같습니다.

 

 


4. JSend 사양을 참고한 ApiResponse

@Component
public class ApiResponse {

    private static final String STATUS_SUCCESS = "success";
    private static final String STATUS_FAIL = "fail";
    private static final String STATUS_ERROR = "error";

    ...
}

response는 "status""message"라는 공통 필드를 가지며, status는 jsend 사양과 마찬가지로 "success", "fail", "error" 세 가지 값을 가집니다.

 

    // 단일 데이터 조회 응답
    {
        "status" : "success",
        "message" : null,
        "data" : { data1 }
    }

    //페이징 정보를 포함한 다중 데이터 조회 응답
    {
        "status" : "success",
        "message" : null,
        "data" : [{data1}, {data2} ... ],
        "page": 1,
        "size": 10,
        "total": 100
    }

    //일반 에러 발생 시 응답
    {
        "status" : "fail",
        "message" : "fail message",
        "errors" : null
    }

    //필드 에러 발생 시 응답
    {
        "status" : "fail",
        "message" : "fail message",
        "errors" : [{error1}, {error2} ... ]
    }

    //예외 발생 시 응답
    {
        "status" : "error",
        "message" : "custom error message"
    }

그리고 각각의 status에 따라 다음과 같은 응답을 내려주면 어떨까 구성해 보았는데요.

이때 고민이 되었던 부분은 과연 response는 동일한 format을 가지고 있어야 하는가입니다.

(data, errors 필드에 대해, 오류가 발생했을 때도 errors 필드가 아닌 data 필드에 오류에 대한 정보를 넣어 사용할 것인지 등)

 

어떠한 방식을 내려주든 응답을 받아 처리하는 클라이언트 측과의 소통은 필요할 것이다.라고 생각하였지만, 만약 공통된 포맷을 사용하게 된다면 아래와 같이 사용할 수 있을 것 같습니다.

 

    //공통된 formet의 api response를 사용한다면,
    {
        "status" : "success" || "fail" || "error",
        "message" : "message",
        "data" : "성공일 경우는 data 객체 또는 배열 (페이지네이션 정보도 포함)" || "실패일 경우는 errors 배열"
    }

 

이러한 응답을 내려주기 위해 spring boot에서 구현한 ApiResponse에 대한 세부적인 코드는 아래 github를 통해 참고할 수 있습니다.

 

 

 

 

< stack overflow, rest api 응답에 대한 논쟁 >

https://stackoverflow.com/questions/12806386/is-there-any-standard-for-json-api-response-format

 

 

< ApiRespons code github 주소 > 

https://github.com/JianChoi-Kor/ttotw/blob/master/client-module/src/main/java/com/project/ttotw/dto/ApiResponse.java

 

< 참고 자료 >

https://blog.lyunho.kim/api-response
http://blog.storyg.co/rest-api-response-body-best-pratics

(조금 더 규격화되고 구체적인 응답을 내려주고 싶을 때는 이 방식도 좋다고 생각됩니다.)