티스토리 뷰
Django로 웹 서비스를 만들다 보면 처음에는 템플릿으로 HTML을 렌더링하는 방식에 익숙해집니다.
하지만 프론트엔드와 백엔드를 분리하거나, 모바일 앱에서 사용할 API를 만들거나, 외부 서비스와 데이터를 주고받아야 한다면 JSON 기반 API가 필요해집니다.
이때 Django에서 REST API를 만들 때 가장 많이 사용하는 도구 중 하나가 Django REST Framework, 줄여서 DRF입니다.
DRF를 처음 공부하면 여러 개념이 한꺼번에 나옵니다.
- Serializer
- ViewSet
- Router
- APIView
- Permission
- Authentication
- Pagination
- Filter
처음부터 모든 기능을 다 외우려고 하면 어렵습니다.
먼저 API 요청이 들어와서 응답이 나가기까지의 흐름을 이해하는 것이 좋습니다.
이번 글에서는 DRF의 핵심 구성요소인 Serializer, ViewSet, Router를 중심으로 기본 흐름을 정리해보겠습니다.
Django REST Framework란?
Django REST Framework는 Django에서 Web API를 만들기 위한 라이브러리입니다.
Django만으로도 JSON 응답을 만들 수는 있습니다.
하지만 API가 많아질수록 반복되는 일이 많아집니다.
예를 들어 게시글 API를 만든다고 해보겠습니다.
- 요청 데이터 파싱
- 입력값 검증
- 모델 객체 생성
- 모델 객체를 JSON으로 변환
- 에러 응답 처리
- 목록 조회
- 상세 조회
- 생성, 수정, 삭제 처리
- 인증과 권한 처리
이런 작업을 매번 직접 만들면 코드가 복잡해집니다.
DRF는 이런 반복 작업을 구조화해서 처리할 수 있게 도와줍니다.
DRF의 기본 흐름은 다음 세 가지를 중심으로 이해하면 좋습니다.
- Serializer: 데이터 변환과 검증
- ViewSet: API 요청 처리
- Router: URL과 ViewSet 연결
DRF 요청 처리 흐름
클라이언트가 DRF API를 호출하면 대략 다음 순서로 처리됩니다.
- 클라이언트가 HTTP 요청을 보냅니다.
- Django URL 라우팅이 요청 URL을 확인합니다.
- Router가 요청을 적절한 ViewSet action으로 연결합니다.
- ViewSet이 요청을 처리합니다.
- Serializer가 요청 데이터를 검증하거나 모델 객체를 JSON으로 변환합니다.
- Model을 통해 데이터베이스를 조회하거나 변경합니다.
- DRF가 JSON 응답을 반환합니다.
단순화하면 다음과 같습니다.
Client
→ URL
→ Router
→ ViewSet
→ Serializer
→ Model
→ Database
→ Response

예제로 사용할 모델
예시로 간단한 게시글 모델을 사용해보겠습니다.
from django.conf import settings
from django.db import models
class Post(models.Model):
author = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE,
related_name="posts",
)
title = models.CharField(max_length=200)
content = models.TextField()
is_public = models.BooleanField(default=True)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
def __str__(self):
return self.title
이 모델은 게시글 하나를 표현합니다.
author: 작성자title: 제목content: 본문is_public: 공개 여부created_at: 생성 시각updated_at: 수정 시각
이 모델을 기준으로 게시글 목록 조회, 상세 조회, 생성, 수정, 삭제 API를 만들어보겠습니다.
Serializer란?
Serializer는 DRF에서 가장 중요한 구성요소 중 하나입니다.
Serializer는 크게 두 가지 일을 합니다.
첫 번째는 모델 객체를 JSON으로 변환하는 일입니다.
Django 모델 객체는 그대로 API 응답으로 보낼 수 없습니다.
클라이언트가 이해할 수 있는 JSON 형태로 바꿔야 합니다.
두 번째는 클라이언트가 보낸 데이터를 검증하는 일입니다.
예를 들어 게시글 생성 요청에서 제목이 비어 있으면 안 됩니다.
본문 길이가 너무 짧아도 막아야 할 수 있습니다.
Serializer는 이런 검증을 담당합니다.
정리하면 Serializer의 역할은 다음과 같습니다.
- 모델 객체를 JSON 응답 데이터로 변환
- JSON 요청 데이터를 Python 데이터로 변환
- 입력값 검증
- 객체 생성과 수정
- API에 노출할 필드 제어
- 읽기 전용 필드와 쓰기 전용 필드 구분
ModelSerializer 사용하기
DRF에서는 Django 모델과 연결된 Serializer를 쉽게 만들 수 있도록 ModelSerializer를 제공합니다.
from rest_framework import serializers
from .models import Post
class PostSerializer(serializers.ModelSerializer):
author_name = serializers.CharField(source="author.username", read_only=True)
class Meta:
model = Post
fields = [
"id",
"author",
"author_name",
"title",
"content",
"is_public",
"created_at",
"updated_at",
]
read_only_fields = [
"id",
"author",
"author_name",
"created_at",
"updated_at",
]
model = Post는 이 Serializer가 Post 모델을 기준으로 동작한다는 뜻입니다.
fields는 API 요청과 응답에서 사용할 필드 목록입니다.
read_only_fields는 클라이언트가 직접 수정할 수 없는 필드입니다.
예를 들어 id, created_at, updated_at은 서버가 관리하는 값입니다.author도 클라이언트가 직접 보내는 값이 아니라, 로그인 사용자 기준으로 서버에서 지정하는 것이 안전합니다.
Serializer에서 검증하기
Serializer는 입력값 검증에도 사용됩니다.
예를 들어 제목은 3자 이상이어야 한다는 규칙을 넣어보겠습니다.
from rest_framework import serializers
from .models import Post
class PostSerializer(serializers.ModelSerializer):
class Meta:
model = Post
fields = [
"id",
"author",
"title",
"content",
"is_public",
"created_at",
"updated_at",
]
read_only_fields = ["id", "author", "created_at", "updated_at"]
def validate_title(self, value):
if len(value.strip()) < 3:
raise serializers.ValidationError("제목은 3자 이상이어야 합니다.")
return value
이제 제목이 너무 짧은 요청이 들어오면 DRF는 검증 오류 응답을 만들 수 있습니다.
{
"title": ["제목은 3자 이상이어야 합니다."]
}
여러 필드를 함께 검증하고 싶다면 validate 메서드를 사용할 수 있습니다.
def validate(self, attrs):
if attrs.get("title") == attrs.get("content"):
raise serializers.ValidationError("제목과 본문은 같을 수 없습니다.")
return attrs
Serializer는 단순히 데이터 모양을 바꾸는 도구가 아니라, API 입력값의 품질을 지키는 도구입니다.
ViewSet이란?
ViewSet은 API 요청 처리 로직을 담는 클래스입니다.
REST API에서는 같은 리소스에 대해 반복되는 패턴이 많습니다.
게시글 API만 봐도 다음 기능이 필요합니다.
- 목록 조회
- 상세 조회
- 생성
- 전체 수정
- 일부 수정
- 삭제
DRF의 ModelViewSet을 사용하면 이런 기본 CRUD 동작을 빠르게 만들 수 있습니다.
from rest_framework import viewsets
from .models import Post
from .serializers import PostSerializer
class PostViewSet(viewsets.ModelViewSet):
queryset = Post.objects.all()
serializer_class = PostSerializer
이 짧은 코드만으로 기본적인 CRUD API의 뼈대를 만들 수 있습니다.
ModelViewSet이 제공하는 주요 action은 다음과 같습니다.
| HTTP 메서드 | URL | ViewSet action | 의미 |
|---|---|---|---|
| GET | /posts/ | list | 목록 조회 |
| POST | /posts/ | create | 생성 |
| GET | /posts/{id}/ | retrieve | 상세 조회 |
| PUT | /posts/{id}/ | update | 전체 수정 |
| PATCH | /posts/{id}/ | partial_update | 일부 수정 |
| DELETE | /posts/{id}/ | destroy | 삭제 |
Router란?
Router는 ViewSet과 URL을 자동으로 연결해주는 도구입니다.
ViewSet은 action을 가지고 있지만, 실제 URL과 연결되어야 클라이언트가 호출할 수 있습니다.
DRF에서는 Router를 사용해 URL 패턴을 자동으로 만들 수 있습니다.
from django.urls import include, path
from rest_framework.routers import DefaultRouter
from .views import PostViewSet
router = DefaultRouter()
router.register("posts", PostViewSet, basename="post")
urlpatterns = [
path("api/", include(router.urls)),
]
이렇게 하면 다음과 같은 URL이 자동으로 연결됩니다.
GET /api/posts/POST /api/posts/GET /api/posts/{id}/PUT /api/posts/{id}/PATCH /api/posts/{id}/DELETE /api/posts/{id}/
Router는 URL을 직접 하나씩 작성하는 수고를 줄여줍니다.
DRF의 기본 CRUD API는 보통 다음 조합으로 시작합니다.
- Model
- Serializer
- ViewSet
- Router
Serializer, ViewSet, Router 역할 비교
세 개념은 함께 쓰이지만 역할이 다릅니다.
| 구성요소 | 역할 | 예시 |
|---|---|---|
| Serializer | 데이터 변환과 검증 | Post 객체를 JSON으로 변환, 요청 데이터 검증 |
| ViewSet | 요청 처리 로직 | 목록 조회, 생성, 수정, 삭제 |
| Router | URL 자동 연결 | /posts/ URL을 PostViewSet에 연결 |
조금 더 쉽게 비유하면 다음과 같습니다.
- Serializer는 데이터 통역사입니다.
- ViewSet은 요청 처리 담당자입니다.
- Router는 URL 안내자입니다.
Router
→ 어떤 ViewSet으로 보낼지 결정
ViewSet
→ 어떤 작업을 할지 결정
Serializer
→ 데이터를 검증하고 변환

게시글 목록 조회 흐름
클라이언트가 게시글 목록을 조회한다고 해보겠습니다.
GET /api/posts/
DRF 내부 흐름은 대략 다음과 같습니다.
- Router가
/api/posts/요청을PostViewSet에 연결합니다. GET요청이므로listaction이 실행됩니다.- ViewSet이
queryset으로 게시글 목록을 가져옵니다. - Serializer가 게시글 목록을 JSON 데이터로 변환합니다.
- DRF가 응답을 반환합니다.
응답 예시는 다음과 같습니다.
[
{
"id": 1,
"author": 10,
"author_name": "kim",
"title": "DRF 이해하기",
"content": "Serializer와 ViewSet을 정리합니다.",
"is_public": true,
"created_at": "2026-05-12T10:00:00Z",
"updated_at": "2026-05-12T10:00:00Z"
}
]
목록 API에서는 보통 페이지네이션, 필터링, 정렬도 함께 고려해야 합니다.
게시글 생성 흐름
게시글을 생성하는 요청은 다음과 같습니다.
POST /api/posts/
Content-Type: application/json
{
"title": "새 게시글",
"content": "새 게시글 본문입니다.",
"is_public": true
}
흐름은 다음과 같습니다.
- Router가 요청을
PostViewSet에 연결합니다. POST요청이므로createaction이 실행됩니다.- Serializer가 요청 데이터를 검증합니다.
- 검증에 성공하면
Post객체를 생성합니다. - Serializer가 생성된 객체를 응답 데이터로 변환합니다.
- 서버가
201 Created응답을 반환합니다.
작성자를 요청 사용자로 지정하고 싶다면 perform_create를 사용할 수 있습니다.
from rest_framework import permissions, viewsets
from .models import Post
from .serializers import PostSerializer
class PostViewSet(viewsets.ModelViewSet):
queryset = Post.objects.all()
serializer_class = PostSerializer
permission_classes = [permissions.IsAuthenticatedOrReadOnly]
def perform_create(self, serializer):
serializer.save(author=self.request.user)
이렇게 하면 클라이언트가 author를 직접 보내지 않아도 서버가 로그인 사용자를 작성자로 지정합니다.
작성자나 소유자 정보는 클라이언트 입력에 맡기기보다 서버에서 결정하는 것이 안전합니다.
get_queryset으로 조회 범위 제한하기
실무에서는 모든 데이터를 항상 보여주지 않습니다.
예를 들어 공개 게시글만 보여주고 싶다면 get_queryset을 사용할 수 있습니다.
class PostViewSet(viewsets.ModelViewSet):
serializer_class = PostSerializer
permission_classes = [permissions.IsAuthenticatedOrReadOnly]
def get_queryset(self):
return Post.objects.filter(is_public=True)
로그인 사용자 본인의 게시글만 보여주는 API라면 다음처럼 작성할 수 있습니다.
def get_queryset(self):
return Post.objects.filter(author=self.request.user)
쿼리 파라미터를 이용해 검색 조건을 반영할 수도 있습니다.
def get_queryset(self):
queryset = Post.objects.all()
search = self.request.query_params.get("search")
if search:
queryset = queryset.filter(title__icontains=search)
return queryset
요청 예시는 다음과 같습니다.
GET /api/posts/?search=django
get_queryset은 요청 사용자, 권한, 쿼리 파라미터에 따라 조회 범위를 조절할 때 자주 사용됩니다.
Permission으로 권한 제어하기
API에서는 인증과 인가가 중요합니다.
DRF에서는 Permission 클래스를 사용해 요청 권한을 제어할 수 있습니다.
예를 들어 누구나 읽을 수 있지만, 작성은 로그인한 사용자만 가능하게 하려면 다음을 사용할 수 있습니다.
from rest_framework import permissions
class PostViewSet(viewsets.ModelViewSet):
queryset = Post.objects.all()
serializer_class = PostSerializer
permission_classes = [permissions.IsAuthenticatedOrReadOnly]
하지만 이 설정만으로 “작성자만 수정 가능”까지 해결되지는 않습니다.
작성자만 수정하거나 삭제할 수 있게 하려면 커스텀 Permission을 만들 수 있습니다.
from rest_framework import permissions
class IsAuthorOrReadOnly(permissions.BasePermission):
def has_object_permission(self, request, view, obj):
if request.method in permissions.SAFE_METHODS:
return True
return obj.author == request.user
ViewSet에 적용합니다.
class PostViewSet(viewsets.ModelViewSet):
queryset = Post.objects.all()
serializer_class = PostSerializer
permission_classes = [
permissions.IsAuthenticatedOrReadOnly,
IsAuthorOrReadOnly,
]
def perform_create(self, serializer):
serializer.save(author=self.request.user)
이제 조회는 누구나 가능하지만, 수정과 삭제는 작성자만 가능하게 만들 수 있습니다.
Serializer를 목록용과 상세용으로 나누기
목록 조회와 상세 조회에서 필요한 필드가 다를 수 있습니다.
예를 들어 목록에서는 제목과 작성자만 보여주고, 상세에서는 본문까지 보여주고 싶을 수 있습니다.
이 경우 Serializer를 분리할 수 있습니다.
class PostListSerializer(serializers.ModelSerializer):
author_name = serializers.CharField(source="author.username", read_only=True)
class Meta:
model = Post
fields = ["id", "author_name", "title", "created_at"]
class PostDetailSerializer(serializers.ModelSerializer):
author_name = serializers.CharField(source="author.username", read_only=True)
class Meta:
model = Post
fields = [
"id",
"author",
"author_name",
"title",
"content",
"is_public",
"created_at",
"updated_at",
]
ViewSet에서는 action에 따라 Serializer를 바꿀 수 있습니다.
class PostViewSet(viewsets.ModelViewSet):
queryset = Post.objects.all()
def get_serializer_class(self):
if self.action == "list":
return PostListSerializer
return PostDetailSerializer
이렇게 하면 목록 응답을 더 가볍게 만들 수 있습니다.
다만 Serializer를 너무 많이 나누면 관리가 어려워질 수 있으므로, 실제 필요가 있을 때 분리하는 것이 좋습니다.
custom action 추가하기
기본 CRUD 외에 추가 API가 필요할 때가 있습니다.
예를 들어 게시글을 공개 처리하는 API를 만들고 싶다고 해보겠습니다.
POST /api/posts/1/publish/
DRF에서는 @action 데코레이터를 사용해 ViewSet에 custom action을 추가할 수 있습니다.
from rest_framework.decorators import action
from rest_framework.response import Response
class PostViewSet(viewsets.ModelViewSet):
queryset = Post.objects.all()
serializer_class = PostSerializer
@action(detail=True, methods=["post"])
def publish(self, request, pk=None):
post = self.get_object()
post.is_public = True
post.save(update_fields=["is_public"])
serializer = self.get_serializer(post)
return Response(serializer.data)
detail=True는 특정 게시글 하나에 대한 action이라는 뜻입니다.
생성되는 URL은 다음과 같습니다.
POST /api/posts/{id}/publish/
custom action은 편리하지만 너무 많이 사용하면 API 구조가 복잡해질 수 있습니다.
먼저 기본 REST 리소스로 표현할 수 있는지 생각하고, 필요한 경우에만 추가하는 것이 좋습니다.
페이지네이션 적용하기
목록 API에서는 페이지네이션이 중요합니다.
게시글이 10개일 때는 전체를 반환해도 괜찮아 보입니다.
하지만 게시글이 10만 개가 되면 전체 목록을 한 번에 반환하는 것은 위험합니다.
DRF에서는 전역 설정으로 페이지네이션을 적용할 수 있습니다.
REST_FRAMEWORK = {
"DEFAULT_PAGINATION_CLASS": "rest_framework.pagination.PageNumberPagination",
"PAGE_SIZE": 20,
}
이렇게 설정하면 목록 API 응답이 페이지네이션 형식으로 반환됩니다.
{
"count": 120,
"next": "http://example.com/api/posts/?page=2",
"previous": null,
"results": [
{
"id": 1,
"title": "첫 번째 글"
}
]
}
목록 API는 처음부터 페이지네이션을 고려하는 것이 좋습니다.
N+1 쿼리 문제 조심하기
DRF Serializer에서 관계 필드를 사용할 때도 N+1 쿼리 문제가 생길 수 있습니다.
예를 들어 게시글 목록에서 작성자 이름을 함께 보여준다고 해보겠습니다.
author_name = serializers.CharField(source="author.username", read_only=True)
게시글 목록을 가져온 뒤 각 게시글의 author에 접근하면, 게시글 수만큼 추가 쿼리가 발생할 수 있습니다.
이 문제를 줄이려면 ViewSet의 queryset에서 select_related를 사용할 수 있습니다.
class PostViewSet(viewsets.ModelViewSet):
serializer_class = PostSerializer
def get_queryset(self):
return Post.objects.select_related("author").all()
다대다 관계나 역참조 관계를 함께 가져와야 한다면 prefetch_related를 고려할 수 있습니다.
def get_queryset(self):
return Post.objects.select_related("author").prefetch_related("tags")
DRF를 사용할 때도 Django ORM 최적화는 여전히 중요합니다.
Serializer가 편리하게 관계 데이터를 보여주더라도, 실제 쿼리가 어떻게 실행되는지 확인해야 합니다.
DRF를 사용할 때 자주 하는 실수
1. Serializer에 모든 필드를 그대로 노출하는 경우
fields = "__all__"은 편리하지만 민감한 필드가 API 응답에 포함될 수 있습니다.
사용자 모델에서 비밀번호 해시, 관리자 여부, 내부 상태값 등이 노출되지 않도록 필요한 필드만 명시하는 것이 좋습니다.
2. 작성자나 소유자 정보를 클라이언트 입력에 맡기는 경우
게시글 작성 API에서 author를 클라이언트가 보내게 하면 다른 사용자 ID를 넣는 시도가 가능해질 수 있습니다.
작성자 정보는 보통 request.user를 기준으로 서버에서 지정해야 합니다.
3. 권한 검사를 빠뜨리는 경우
로그인만 확인하고 작성자 여부를 확인하지 않으면 다른 사용자의 데이터를 수정할 수 있는 문제가 생길 수 있습니다.
인증과 인가는 반드시 구분해야 합니다.
4. 페이지네이션 없이 목록을 반환하는 경우
초기에는 데이터가 적어서 괜찮아 보이지만, 데이터가 늘어나면 응답 속도와 서버 부담이 커질 수 있습니다.
목록 API에는 페이지네이션을 기본으로 고려하는 것이 좋습니다.
5. 관계 필드를 쓰면서 쿼리를 확인하지 않는 경우
Serializer에서 관계 필드를 사용하면 편리하지만, queryset 최적화가 없으면 N+1 문제가 생길 수 있습니다.
Django Debug Toolbar나 쿼리 로그를 활용해 실제 실행 쿼리를 확인하는 습관이 필요합니다.
DRF를 공부할 때 추천하는 순서
DRF를 처음 공부한다면 다음 순서가 좋습니다.
- REST API의 기본 개념을 이해합니다.
- Serializer가 데이터 변환과 검증을 담당한다는 점을 익힙니다.
- ModelSerializer로 간단한 응답을 만들어봅니다.
- ViewSet으로 CRUD API를 만들어봅니다.
- Router로 URL을 자동 연결해봅니다.
perform_create로 요청 사용자와 객체를 연결해봅니다.- Permission으로 인증과 권한을 적용합니다.
- 페이지네이션, 필터링, 검색을 적용합니다.
- Serializer를 목록용과 상세용으로 분리해봅니다.
- N+1 쿼리와 ORM 최적화를 확인합니다.
처음부터 모든 기능을 외우기보다, 요청이 들어와서 응답이 나가기까지의 흐름을 먼저 이해하는 것이 좋습니다.
정리
Django REST Framework는 Django에서 REST API를 만들기 위한 강력한 도구입니다.
처음에는 개념이 많아 보이지만, 기본 흐름은 Serializer, ViewSet, Router를 중심으로 이해하면 됩니다.
핵심을 정리하면 다음과 같습니다.
- DRF는 Django에서 Web API를 만들기 위한 라이브러리입니다.
- Serializer는 데이터 변환과 입력값 검증을 담당합니다.
- ModelSerializer는 Django 모델 기반 Serializer를 쉽게 만들 수 있게 해줍니다.
- ViewSet은 목록 조회, 상세 조회, 생성, 수정, 삭제 같은 API 처리 로직을 묶어줍니다.
- Router는 ViewSet과 URL을 자동으로 연결합니다.
ModelViewSet을 사용하면 기본 CRUD API를 빠르게 만들 수 있습니다.- 작성자 같은 값은 클라이언트 입력보다 서버의
request.user기준으로 지정하는 것이 안전합니다. - Permission은 인증된 사용자가 특정 작업을 할 수 있는지 판단하는 데 사용됩니다.
- 목록 API에는 페이지네이션, 필터링, 정렬을 고려해야 합니다.
- Serializer에서 관계 필드를 사용할 때는 N+1 쿼리 문제를 조심해야 합니다.
DRF를 잘 이해하면 Django로 백엔드 API를 훨씬 빠르고 일관되게 만들 수 있습니다.
다만 편리한 도구일수록 어떤 데이터가 노출되고, 어떤 권한 검사가 필요하며, 실제 쿼리가 어떻게 실행되는지 함께 확인하는 습관이 중요합니다.
참고 링크
'백엔드 실무' 카테고리의 다른 글
| GitHub Actions로 CI/CD 이해하기: 테스트부터 Docker 이미지 빌드까지 (0) | 2026.05.12 |
|---|---|
| Celery와 작업 큐 이해하기: 오래 걸리는 작업을 백그라운드로 보내는 방법 (0) | 2026.05.12 |
| Django 템플릿에서 DB 쿼리를 조심해야 하는 이유 (0) | 2026.05.11 |
| Django 쿼리 최적화 이해하기: N+1 문제와 select_related, prefetch_related (0) | 2026.05.11 |
| 서버 단 캐시 이해하기: Redis, TTL, 캐시 무효화까지 (0) | 2026.05.11 |
