DRF의 Response vs Django의 JsonResponse: "그냥" 쓰던 것들의 정체

대부분의 Django 개발자도 그러하겠지만, 나는 Django 프로젝트에서는 거의 99%는 DRF (Django REST Framework) 패키지를 함께 사용한다.

그러다보니 서버에 응답을 반환할 때, 그 응답이 API 응답이던, 템플릿에서 요청한 단순 JSON 요청이든 신경 쓰지 않고 구조화된 응답 클래스를 반환하는 Response를 사용한다. 정말 그냥 아무 생각 없이 사용한다.

왜냐면 시리얼라이저와 궁합이 매우 좋을 뿐 아니라, 모든 응답에 기본적으로 이 Response 클래스를 사용하면 어떤 고민과 생각도 할 필요가 없기 때문이다.

그런데 오늘 문득... "그러고 보니 왜 Response는 django.http.JsonResponse와는 도대체 어디가 얼마만큼 다를까?" 라는 궁금증이 들었다. 그래서 오늘은 그 궁금증을 해결해 보려 한다.


1. 태생부터 다르다: 정적(Static)인가, 유연(Flexible)인가?

JsonResponse와 Response의 차이 도식화

먼저 두 녀석의 족보를 살펴보니 재미있는 차이가 있었다.

  • JsonResponse: Django의 HttpResponse를 상속받는다. 목적은 아주 명확하다. "데이터를 받아서 json.dumps()로 직렬화한 뒤, Content-Type을 application/json으로 박아서 내보낸다." 끝이다. 아주 단순하고 직관적인 녀석이다.

  • DRF Response: 얘는 SimpleTemplateResponse를 상속받는다. 응? 뭐지?? 왜 템플릿 관련 클래스를 상속받을까? 여기서부터 Response의 정체가 드러난다. 내가 아무 생각없이 쓰던 이 녀석은 최종 결과물을 미리 결정하지 않은 '렌더링 전 데이터의 컨테이너' 였다!!


2. 핵심 차이: 콘텐츠 협상 (Content Negotiation)

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)의 편의성

JsonResponse를 쓸 때는 우리가 데이터를 일일이 파이썬 딕셔너리나 리스트 형태로 '정제'해서 넘겨줘야 한다. 반면, Response는 DRF의 Serializer와 찰떡궁합이다.

간단한 예시를 보자. 만약 블로그 포스트 정보를 반환해야 한다면 어떨까?

Case A: JsonResponse (수동 노가다 버전)

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 (자동화 버전)

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를 써도 될까?

100% 내 주관적인 경험에 의한 결론이지만, 적어도 나의 응답은 "전혀 문제없다. 오히려 권장한다." 이다.

물론 기술적으로 따지자면 Response는 Content Negotiation 과정을 거치고 여러 렌더러를 체크하기 때문에 JsonResponse보다 아주 미세하게 연산이 더 들어갈 수 있다. 하지만 그 차이는 현대 인프라(심지어 내가 돌리는 Raspberry Pi 5에서도!)에서 체감하기 힘든 수준이다.

오히려 Response를 계속 사용할 때 얻는 이점이 훨씬 크다.

  1. 일관성: 프로젝트 전체에서 응답 형식을 통일할 수 있다.
  2. 디버깅: 브라우저로 접속했을 때 Browsable API를 통해 데이터를 바로 확인하기 편하다.
  3. 유연성: 나중에 응답 포맷을 XML이나 YAML로 확장해야 할 때 코드 수정 없이 설정만으로 대응 가능하다.

마치며: 차이를 알고 나니 상쾌하다

오늘 공부를 통해 내린 결론은 이렇다.

"결국 큰 성능 차이가 있는 것도 아니고, DRF 환경이라면 그냥 쓰던 대로 Response를 쓰는 게 정신 건강에 이롭다."

하지만 내가 매일 쓰던 도구가 내부적으로 어떻게 돌아가는지, 왜 굳이 JsonResponse가 아닌 Response라는 별도의 클래스를 써야 했는지 그 이유를 알고 나니 기분이 아주 상쾌하다. 역시 개발자는 "왜?"라는 질문을 멈추지 않을 때 한 걸음 더 성장하는 것 같다.

자, 이제 정체도 알았으니 속 시원하게 Response를 더 잘 써야겠다.


혹시 저처럼 Django와 DRF의 미묘한 차이 때문에 밤잠 설쳤던 분들이 계신다면 이 글이 도움이 되었길 바랍니다. 궁금한 점이나 의견은 댓글로 남겨주세요!

이 글이 유익했다면 Follow를 해주세요! Follow는 Mikihands Blog Flatform에 가입해서 계정을 만드신 후 가능합니다.
블로그에 글을 쓰고 누군가가 나의 글을 읽어주는 경험은... 꽤 재미있습니다.