일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |
- Graph
- C programming
- Algorithm
- Stack
- 메모리구조
- stream
- C 언어 코딩 도장
- Selection Sorting
- 이스케이프 문자
- Serialization
- 윤성우 열혈자료구조
- s
- buffer
- 윤성우의 열혈 자료구조
- datastructure
- insertion sort
- list 컬렉션
- coding test
- 혼자 공부하는 C언어
- R
- JSON
- 이것이 자바다
- 알기쉬운 알고리즘
- Today
- Total
Engineering Note
[SW Engineering] AJAX 응답 타입 불일치로 인한 TypeError 해결기 본문
[SW Engineering] AJAX 응답 타입 불일치로 인한 TypeError 해결기
Software Engineer Kim 2025. 9. 27. 15:33이어 지는 글
- 구체적인 사례로 보는 JS 에러 메세지 읽는 법(https://techbook11.tistory.com/713)
장바구니 주문 기능을 개발하던 중 발생한 흔한 실수와 그 해결 과정을 공유합니다. 프론트엔드와 백엔드 간의 데이터 타입 불일치로 인해 발생한 문제로, 많은 개발자가 겪을 수 있는 사례입니다.
문제 상황
사용자가 장바구니에서 주문 버튼을 클릭했을 때 다음과 같은 현상이 발생했습니다.
사용자 관점의 문제:
- 주문버튼을 눌렀지만 아무런 반응이 없음
- 에러메세지가 표시되지 않아 무엇이 잘못되었는지 알 수 없음
시스템 관점의 문제:
- HTTP 400 Bad Request 응답
- Javascript TypeError 발생
에러 분석
1단계 : 프론트엔드 에러 확인
브라우저 개발자 도구에서 다음 에러를 확인했습니다.
Uncaught TypeError: Cannot read properties of undefined (reading 'message')
at Object.error (cart:163:50)
at c (jquery-3.5.1.min.js:2:28294)
at Object.fireWith [as rejectWith] (jquery-3.5.1.min.js:2:29039)
at l (jquery-3.5.1.min.js:2:79825)
at XMLHttpRequest.<anonymous> (jquery-3.5.1.min.js:2:82254)
네트워크 탭에서는 '/cart/orders' API 호출이 HTTP 400 상태로 실패한 것을 확인했습니다.
2단계: 서버 로그 분석
처음에는 info 레벨로그에서 요청 로그를 찾을 수 없어 컨트롤러에 요청이 도달하지 않은 것으로 판단했습니다. 하지만 debug 레벨로 로그를 변경한 후 확인해보니:
- 요청은 정상적으로 컨트롤러로 도달
- 서버의 유효성 검사에서 잘못된 요청으로 400 응답 반환
서버 에러 로그
2025-09-27T15:16:22.865+09:00 DEBUG 13417 --- [shop] [nio-8080-exec-9] o.s.security.web.FilterChainProxy : Secured POST /cart/orders
2025-09-27T15:16:22.865+09:00 DEBUG 13417 --- [shop] [nio-8080-exec-9] o.s.web.servlet.DispatcherServlet : POST "/cart/orders", parameters={}
2025-09-27T15:16:22.865+09:00 DEBUG 13417 --- [shop] [nio-8080-exec-9] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped to com.shop.controller.CartController#orderCartItem(CartOrderDto, Principal)
2025-09-27T15:16:22.865+09:00 DEBUG 13417 --- [shop] [nio-8080-exec-9] o.j.s.OpenEntityManagerInViewInterceptor : Opening JPA EntityManager in OpenEntityManagerInViewInterceptor
2025-09-27T15:16:22.866+09:00 DEBUG 13417 --- [shop] [nio-8080-exec-9] m.m.a.RequestResponseBodyMethodProcessor : Read "application/json;charset=UTF-8" to [com.shop.dto.CartOrderDto@22f3ccf0]
2025-09-27T15:16:22.866+09:00 DEBUG 13417 --- [shop] [nio-8080-exec-9] o.s.w.s.m.m.a.HttpEntityMethodProcessor : Using 'application/json', given [application/json, text/javascript, */*;q=0.01] and supported [text/plain, */*, application/json, application/*+json, application/yaml]
2025-09-27T15:16:22.866+09:00 DEBUG 13417 --- [shop] [nio-8080-exec-9] o.s.w.s.m.m.a.HttpEntityMethodProcessor : Writing ["주문할 상품을 선택해주세요."]
2025-09-27T15:16:22.866+09:00 DEBUG 13417 --- [shop] [nio-8080-exec-9] o.j.s.OpenEntityManagerInViewInterceptor : Closing JPA EntityManager in OpenEntityManagerInViewInterceptor
2025-09-27T15:16:22.866+09:00 DEBUG 13417 --- [shop] [nio-8080-exec-9] o.s.web.servlet.DispatcherServlet : Completed 400 BAD_REQUEST
백엔드 응답 코드
if(cartOrderDtoList == null || cartOrderDtoList.size() == 0){
return new ResponseEntity<String>("주문할 상품을 선택해주세요.", HttpStatus.BAD_REQUEST);
}
백엔드에서는 에러메세지를 String 타입으로 응답했습니다.
프론트 엔드 에러 처리 코드
error : function(jqXHR, status, error){
if(jqXHR.status == '401'){
alert('로그인 후 이용해주세요');
location.href='/members/login';
} else{
console.log("주문 실패")
alert(jqXHR.responseJSON.message); // 여기서 TypeError 발생
}
}
프론트엔드에서는 jqXHR.responseJSON.message로 JSON 객체의 message 속성에 접근하려 했지만, 서버에서는 단순 문자열을 반환했기 때문에 jqXHR.responseJSON이 undefined가 되어 TypeError가 발생했습니다.
해결 방법
초기 임시 해결 방법 : 프론트엔드 수정
빠른 해결을 위해 프론트엔드에서 문자열 응답을 처리하도록 수정했습니다.
error : function(jqXHR, status, error){
if(jqXHR.status == '401'){
alert('로그인 후 이용해주세요');
location.href='/members/login';
} else{
console.log("주문 실패")
alert(jqXHR.responseText); // responseJSON.message → responseText로 변경
}
}
최종 해결책 : 백엔드 수정
// 에러 응답 DTO 클래스 생성
public class ErrorResponse {
private String message;
public ErrorResponse(String message) {
this.message = message;
}
// getter, setter
}
// 컨트롤러 수정
if(cartOrderDtoList == null || cartOrderDtoList.size() == 0){
ErrorResponse errorResponse = new ErrorResponse("주문할 상품을 선택해주세요.");
return new ResponseEntity<ErrorResponse>(errorResponse, HttpStatus.BAD_REQUEST);
}
임시 수정 이후, 일관된 API 응답 제공을 위해 에러 응답 DTO 클래스를 만들어 JSON 형태로 응답하도록 코드 품질 개선 과정도 거쳤습니다.
배운 점과 최종 해결책
1. API 응답 형식 통일의 중요성
- 성공 응답과 에러 응답의 데이터 타입을 일관되게 유지
- API 문서에 응답 형식을 명확히 정의
2. 방어적 프로그래밍
- 프론트엔드에서 서버 응답을 처리할 때 타입 체크
error : function(jqXHR, status, error){
if(jqXHR.status == '401'){
alert('로그인 후 이용해주세요');
location.href='/members/login';
} else{
console.log("주문 실패");
// 안전한 에러 메시지 처리
let errorMessage = "주문 처리 중 오류가 발생했습니다.";
if(jqXHR.responseJSON && jqXHR.responseJSON.message) {
errorMessage = jqXHR.responseJSON.message;
} else if(jqXHR.responseText) {
errorMessage = jqXHR.responseText;
}
alert(errorMessage);
}
}
결론
이번 사례는 프론트엔드와 백엔드 간의 데이터 타입 불일치로 인한 일반적인 문제였습니다. 단순한 수정으로 해결되었지만, 근본적으로는 API 설계의 일관성과 유효성 타입체크 등 방어적 프로그래밍의 중요성을 다시 한번 깨닫게 해준 경험이었습니다.