## DRF의 Response vs Django의 JsonResponse: "그냥" 쓰던 것들의 정체 {#sec-415c48e5bc9d} 대부분의 Django 개발자도 그러하겠지만, 나는 [[Django]] 프로젝트에서는 거의 99%는 DRF ([[Django REST Framework]]) 패키지를 함께 사용한다. 그러다보니 서버에 응답을 반환할 때, 그 응답이 API 응답이던, 템플릿에서 요청한 단순 JSON 요청이든 신경 쓰지 않고 구조화된 응답 클래스를 반환하는 `Response`를 사용한다. 정말 그냥 아무 생각 없이 사용한다. 왜냐면 시리얼라이저와 궁합이 매우 좋을 뿐 아니라, 모든 응답에 기본적으로 이 `Response` 클래스를 사용하면 어떤 고민과 생각도 할 필요가 없기 때문이다. 그런데 오늘 문득... **"그러고 보니 왜 Response는 django.http.JsonResponse와는 도대체 어디가 얼마만큼 다를까?"** 라는 궁금증이 들었다. 그래서 오늘은 그 궁금증을 해결해 보려 한다. --- ## **1. 태생부터 다르다: 정적(Static)인가, 유연(Flexible)인가?** {#sec-2451e29dd2a3} ![JsonResponse와 Response의 차이 도식화](/media/whitedec/blog_img/66a44f9db4b44419a1a34d72608f8eb9.webp) 먼저 두 녀석의 족보를 살펴보니 재미있는 차이가 있었다. * **JsonResponse:** Django의 `HttpResponse`를 상속받는다. 목적은 아주 명확하다. "데이터를 받아서 `json.dumps()`로 직렬화한 뒤, Content-Type을 `application/json`으로 박아서 내보낸다." 끝이다. 아주 단순하고 직관적인 녀석이다. * **DRF Response:** 얘는 `SimpleTemplateResponse`를 상속받는다. 응? 뭐지?? 왜 템플릿 관련 클래스를 상속받을까? 여기서부터 `Response`의 정체가 드러난다. 내가 아무 생각없이 쓰던 이 녀석은 최종 결과물을 미리 결정하지 않은 **'렌더링 전 데이터의 컨테이너'** 였다!! --- ## **2. 핵심 차이: 콘텐츠 협상 (Content Negotiation)** {#sec-4b3b34038277} `JsonResponse`는 "나는 무조건 JSON이야!"라고 답정너 스타일이지만, DRF의 `Response`는 아주 유연하고 똑똑하다. DRF의 `Response`는 클라이언트가 뭘 원하는지 물어본다. (이걸 **Content Negotiation**이라고 한다.) 클라이언트가 헤더에 `Accept: text/html`을 보내면 우리에게 익숙한 예쁜 'Browsable API' 화면을 보여주고, `Accept: application/json`을 보내면 순수 JSON 데이터만 보낸다. 즉, `Response` 클래스 자체는 데이터를 담고만 있고, 실제 어떤 형태로 보여줄지는 DRF의 **Renderer**들이 결정하는 구조였다. 우리가 아무 생각 없이 `Response`를 써도 상황에 맞는 응답이 나갔던 건, DRF가 뒤에서 열심히 협상을 해준 덕분이다. 엄청 놀라운 발견과 깨달음이 아닐 수 없었다. 정말 치밀하게 만들어진 도구구나 하는 생각이 든다. 이런 순간마다 Django/DRF의 개발자 및 기여자들에게 잠시 고마운 마음을 수 초간 가져본다. --- ## **3. 직렬화(Serialization)의 편의성** {#sec-f361c5e0b07e} `JsonResponse`를 쓸 때는 우리가 데이터를 일일이 파이썬 딕셔너리나 리스트 형태로 '정제'해서 넘겨줘야 한다. 반면, `Response`는 DRF의 Serializer와 찰떡궁합이다. 간단한 예시를 보자. 만약 블로그 포스트 정보를 반환해야 한다면 어떨까? **Case A: JsonResponse (수동 노가다 버전)** ```python from django.http import JsonResponse def post_detail(request, pk): post = Post.objects.get(pk=pk) # 데이터를 하나하나 딕셔너리로 만들어줘야 한다. # 필드가 20개라면? 음.. 짜증이 좀 날 것 같다. data = { "title": post.title, "content": post.content, "created_at": post.created_at.strftime("%Y-%m-%d"), # 날짜 포맷도 직접! } return JsonResponse(data) ``` **Case B: DRF Response (자동화 버전)** ```python from rest_framework.response import Response from rest_framework.decorators import api_view @api_view(['GET']) def post_detail(request, pk): post = Post.objects.get(pk=pk) serializer = PostSerializer(post) # .data를 그대로 던지면 끝. # 복잡한 관계(Foreign Key)나 날짜 포맷팅은 시리얼라이저가 알아서 한다. return Response(serializer.data) ``` 보이는가? `JsonResponse`는 우리가 직접 재료를 다듬어서 접시에 담아줘야 하는 반면, `Response`는 복잡한 모델 인스턴스나 쿼리셋을 시리얼라이저라는 '자동 조리기'에 넣고, 그 결과물을 그대로 던지면 된다. 렌더러가 알아서 최종적인 JSON 문자열로 예쁘게 변환해 주기 때문이다. 가끔 성격꼼꼼한 ChatGPT같은 AI 하고 작업하다보면, 과잉 친절인지 과잉방어인지, 일일이 응답데이터를 만들고 결국에 Response 를 호출해서 응답을 반환하는 것을 몇 번 목격한적이 있다. 효율성을 중시하는 개발자들은 그런 코드를 보면 속이 좀 불편해질 것 같다. 이제는 AI가 그런 코드를 가져오면 과감히 고치자. --- ## **그럼, 간단한 JSON 응답에도 계속 Response를 써도 될까?** {#sec-ff7e587ad4e3} 100% 내 주관적인 경험에 의한 결론이지만, 적어도 나의 응답은 **"전혀 문제없다. 오히려 권장한다."** 이다. 물론 기술적으로 따지자면 `Response`는 Content Negotiation 과정을 거치고 여러 렌더러를 체크하기 때문에 `JsonResponse`보다 아주 미세하게 연산이 더 들어갈 수 있다. 하지만 그 차이는 현대 인프라(심지어 내가 돌리는 Raspberry Pi 5에서도!)에서 체감하기 힘든 수준이다. 오히려 `Response`를 계속 사용할 때 얻는 이점이 훨씬 크다. 1. **일관성:** 프로젝트 전체에서 응답 형식을 통일할 수 있다. 2. **디버깅:** 브라우저로 접속했을 때 Browsable API를 통해 데이터를 바로 확인하기 편하다. 3. **유연성:** 나중에 응답 포맷을 XML이나 YAML로 확장해야 할 때 코드 수정 없이 설정만으로 대응 가능하다. --- ## **마치며: 차이를 알고 나니 상쾌하다** {#sec-b1ff40748b2c} 오늘 공부를 통해 내린 결론은 이렇다. > **"결국 큰 성능 차이가 있는 것도 아니고, DRF 환경이라면 그냥 쓰던 대로 Response를 쓰는 게 정신 건강에 이롭다."** 하지만 내가 매일 쓰던 도구가 내부적으로 어떻게 돌아가는지, 왜 굳이 `JsonResponse`가 아닌 `Response`라는 별도의 클래스를 써야 했는지 그 이유를 알고 나니 기분이 아주 상쾌하다. 역시 개발자는 "왜?"라는 질문을 멈추지 않을 때 한 걸음 더 성장하는 것 같다. 자, 이제 정체도 알았으니 속 시원하게 `Response`를 더 잘 써야겠다. --- 혹시 저처럼 Django와 DRF의 미묘한 차이 때문에 밤잠 설쳤던 분들이 계신다면 이 글이 도움이 되었길 바랍니다. 궁금한 점이나 의견은 댓글로 남겨주세요! 이 글이 유익했다면 Follow를 해주세요! Follow는 [Mikihands Blog Flatform](https://blog.mikihands.com/)에 가입해서 계정을 만드신 후 가능합니다. 블로그에 글을 쓰고 누군가가 나의 글을 읽어주는 경험은... 꽤 재미있습니다.