[Spring] ResponseEntity / ApiResponse

밍맹뭉·2일 전

SpringBoot

목록 보기
6/6
post-thumbnail

부트캠프에서 프로젝트를 진행할 때는 API 응답 처리에 대해 깊이 고민하기보다는, 기존에 사용하던 코드를 그대로 가져다 쓰는 경우가 많았다.
하지만 이번에 사이드 프로젝트를 혼자 진행하면서 백엔드와 프론트를 모두 다루게 되었고, 그 과정에서 응답 처리 방식에 대해 다시 생각해보게 되었다. 특히 스프링에서는 ResponseEntity를 사용하는 방식과 ApiResponse와 같은 커스텀 응답 객체를 사용하는 방식이 많이 쓰인다는 것을 알게 되었다.
두 방식 모두 자주 사용되지만, 각각의 역할과 사용 목적이 명확히 다르기 때문에 상황에 맞게 선택하는 것이 중요하다.
이번 글에서는 ResponseEntity와 ApiResponse의 차이를 정리하고, 어떤 상황에서 어떤 방식을 사용하는 것이 좋은지 정리해보려고 한다.

ResponseEntity

ResponseEntity는 Spring Framework에서 제공하는 클래스로, HTTP 응답(Response)의 전체 구성 요소(상태 코드, 헤더, 바디)를 제어하기 위해 사용된다.

public class ResponseEntity<T> extends HttpEntity<T> {

	private final HttpStatusCode status;


	public ResponseEntity(HttpStatusCode status) {
		this(null, (HttpHeaders) null, status);
	}

	public ResponseEntity(@Nullable T body, HttpStatusCode status) {
		this(body, (HttpHeaders) null, status);
	}
    `
    `
    `
}

@GetMapping("/{id}")
public ResponseEntity<User> getUser(@PathVariable Long id) {
    User user = userService.findById(id);
    return ResponseEntity.ok(user); // 200 OK와 함께 데이터 반환
}

@PostMapping("/join")
public ResponseEntity<Void> signup(@RequestBody UserDto userDto) {
    userService.save(userDto);
    return ResponseEntity.status(HttpStatus.CREATED).build(); // 201 Created 반환
}

특징

  • HttpEntity를 상속받는 클래스로, 결과 데이터(Body)뿐만 아니라 HTTP 상태 코드(Status Code)HTTP 헤더(Headers)를 직접 설정할 수 있음

    • Status Code: 200(OK), 201(Created), 400(Bad Request), 404(Not Found) 등을 명시적으로 반환

    • Headers: Content-Type, Cache-Control 등 브라우저나 클라이언트가 해석해야 할 메타 데이터 설정

    • Body: 클라이언트에게 보낼 실제 데이터 (객체, 문자열, JSON 등)

  • 프론트엔드 개발자가 HTTP 상태 코드만 보고 빠르게 성공/실패 여부 판단할 수 있음

  • ResponseEntity<T> 처럼 제네릭을 통해 데이터 타입 자유롭게 담을 수 있음


ApiResponse

ApiResponse는 Spring에 내장된 클래스가 아니라, 개발자가 공통적인 응답 규격을 정의하기 위해 직접 만든 커스텀 DTO이다.

API마다 응답하는 데이터의 형태가 제각각이면 프론트엔드에서 데이터를 처리할 때마다 조건문을 써야 하는 번거로움이 생기는데, 이를 방지하기 위해 사용한다.

특징

  • 클라이언트에게 내려줄 응답 JSON 구조를 통일
  • 성공/실패 포맷을 일관되게 유지
  • HTTP 상태 코드만으로는 부족한 디테일한 정보 전달 가능
@Getter
@AllArgsConstructor
public class ApiResponse<T> {
    private boolean success;
    private String message;
    private T data;

    public static <T> ApiResponse<T> success(T data) {
        return new ApiResponse<>(true, "성공", data);
    }

    public static ApiResponse<?> error(String message) {
        return new ApiResponse<>(false, message, null);
    }
}

위의 코드는 예시이고 팀 컨벤션에 따라 자유롭게 수정하여 사용하는 편이다.


핵심 차이

구분ResponseEntityApiResponse
레벨HTTP 레벨데이터(JSON) 레벨
역할상태코드/헤더/바디 제어응답 바디 구조 통일
제공Spring 기본 제공직접 설계
사용 목적REST 표준 응답클라이언트 일관성

언제 뭘 쓸지?

✔ ResponseEntity만 써도 되는 경우

  • 간단한 API
  • 구조 통일 필요 없을 때

✔ ApiResponse까지 쓰는 경우 (실무 대부분)

  • 프론트와 협업
  • 에러/성공 포맷 통일 필요
  • Swagger 문서 명확히 하고 싶을 때

실무 권장 패턴

ResponseEntity의 Body에 ApiResponse를 담아서 반환하는 방식이 가장 RESTful하고 깔끔하다.

@GetMapping("/api/v1/users/{id}")
public ResponseEntity<ApiResponse<UserResponse>> getUser(@PathVariable Long id) {
    UserResponse user = userService.findById(id);
    
    // HTTP 상태는 200 OK로, 바디는 우리가 약속한 ApiResponse 규격으로
    return ResponseEntity.ok(ApiResponse.success(user));
}

이렇게 하면 클라이언트는 먼저 HTTP 상태 코드로 네트워크 상태를 파악하고, 그 다음 JSON 바디(ApiResponse)를 열어서 실제 서비스 데이터와 메시지를 확인하게 된다.

profile
배우고 기록하며 성장하고 있습니다 👩‍💻

0개의 댓글