Django REST Framework를 활용한 확장 가능한 API 서버 구축 가이드

Django REST Framework 핵심 아키텍처와 실전 활용

Django REST Framework(DRF)는 Django 생태계에서 RESTful 웹 API를 구축하기 위한 표준 라이브러리입니다. ORM 객체를 JSON 등의 포맷으로 변환하는 직렬화(Serialization) 기능과 다양한 수준의 뷰 클래스, 그리고 강력한 인증 및 권한 관리 시스템을 제공하여 개발 생산성을 극대화합니다.

1. 환경 설정 및 데이터 모델링

DRF와 주요 확장 패키지를 설치하고, 블로그 시스템을 예시로 데이터 모델을 설계합니다.

pip install django djangorestframework djangorestframework-simplejwt drf-yasg django-filter

모델 정의 (models.py)

from django.db import models

class Category(models.Model):
    name = models.CharField(max_length=50, verbose_name='카테고리명')
    description = models.TextField(blank=True, verbose_name='설명')

    class Meta:
        db_table = 'blog_category'
        verbose_name = '카테고리'

    def __str__(self):
        return self.name

class Article(models.Model):
    STATUS_CHOICES = (
        ('draft', '초안'),
        ('published', '발행'),
    )
    title = models.CharField(max_length=200, verbose_name='제목')
    content = models.TextField(verbose_name='내용')
    status = models.CharField(max_length=10, choices=STATUS_CHOICES, default='draft')
    views_count = models.PositiveIntegerField(default=0, verbose_name='조회수')
    category = models.ForeignKey(Category, on_delete=models.CASCADE, related_name='articles')
    created_at = models.DateTimeField(auto_now_add=True)

    class Meta:
        db_table = 'blog_article'
        verbose_name = '게시물'
        ordering = ['-created_at']

2. 직렬화(Serialization)와 데이터 유효성 검사

직렬화는 복잡한 Django QuerySet이나 Model 인스턴스를 Python 기본 데이터 타입으로 변환하여 JSON 등으로 렌더링하기 쉽게 만드는 과정입니다. 반대로 역직렬화는 들어온 요청 데이터를 파싱하고 유효성을 검사한 후 Model 인스턴스로 복원합니다.

ModelSerializer 활용 (serializers.py)

from rest_framework import serializers
from .models import Category, Article

class CategorySerializer(serializers.ModelSerializer):
    class Meta:
        model = Category
        fields = ['id', 'name', 'description']

class ArticleSerializer(serializers.ModelSerializer):
    category_name = serializers.ReadOnlyField(source='category.name')

    class Meta:
        model = Article
        fields = ['id', 'title', 'content', 'status', 'views_count', 'category', 'category_name', 'created_at']
        read_only_fields = ['views_count', 'created_at']
        extra_kwargs = {
            'title': {'min_length': 5, 'max_length': 200},
        }

    def validate_title(self, value):
        if '금지어' in value:
            raise serializers.ValidationError("제목에 부적절한 단어가 포함되어 있습니다.")
        return value

    def validate(self, attrs):
        if attrs.get('status') == 'published' and len(attrs.get('content', '')) < 50:
            raise serializers.ValidationError("발행 상태의 게시물은 내용이 50자 이상이어야 합니다.")
        return attrs

3. 뷰(View) 계층과 라우팅 최적화

DRF는 APIView부터 GenericAPIView, Mixin, 그리고 ViewSet에 이르기까지 다양한 추상화 레벨의 뷰 클래스를 제공합니다. ViewSet을 사용하면 CRUD 로직을 하나의 클래스에 응집시키고, Router를 통해 URL 매핑을 자동화할 수 있습니다.

ViewSet과 Router 설정 (views.py 및 urls.py)

# views.py
from rest_framework import viewsets, filters
from rest_framework.decorators import action
from rest_framework.response import Response
from django_filters.rest_framework import DjangoFilterBackend
from .models import Article
from .serializers import ArticleSerializer

class ArticleViewSet(viewsets.ModelViewSet):
    queryset = Article.objects.all()
    serializer_class = ArticleSerializer
    filter_backends = [DjangoFilterBackend, filters.SearchFilter, filters.OrderingFilter]
    filterset_fields = ['status', 'category']
    search_fields = ['title', 'content']
    ordering_fields = ['created_at', 'views_count']

    @action(detail=True, methods=['post'])
    def publish(self, request, pk=None):
        article = self.get_object()
        article.status = 'published'
        article.save()
        return Response({'status': 'published'})

# urls.py
from django.urls import path, include
from rest_framework.routers import DefaultRouter
from .views import ArticleViewSet

router = DefaultRouter()
router.register(r'articles', ArticleViewSet, basename='article')

urlpatterns = [
    path('api/v1/', include(router.urls)),
]

4. 보안: JWT 인증과 커스텀 권한

Stateless한 API 서버를 위해 JWT(JSON Web Token) 인증을 주로 사용하며, 비즈니스 로직에 맞는 세밀한 권한 제어를 위해 커스텀 Permission 클래스를 구현합니다.

JWT 설정 및 커스텀 권한 (settings.py 및 permissions.py)

# settings.py 일부
REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': (
        'rest_framework_simplejwt.authentication.JWTAuthentication',
    ),
    'DEFAULT_PERMISSION_CLASSES': (
        'rest_framework.permissions.IsAuthenticatedOrReadOnly',
    ),
}

# permissions.py
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

5. 페이징(Pagination)과 전역 예외 처리

대용량 데이터 조회 시 성능 저하를 방지하기 위해 페이징을 적용하고, 클라이언트에게 일관된 에러 포맷을 제공하기 위해 전역 예외 핸들러를 구성합니다.

커스텀 페이징 및 예외 핸들러

# pagination.py
from rest_framework.pagination import PageNumberPagination
from rest_framework.response import Response

class StandardResultsSetPagination(PageNumberPagination):
    page_size = 20
    page_size_query_param = 'limit'
    max_page_size = 100

    def get_paginated_response(self, data):
        return Response({
            'next': self.get_next_link(),
            'previous': self.get_previous_link(),
            'count': self.page.paginator.count,
            'results': data
        })

# exceptions.py
from rest_framework.views import exception_handler

def custom_exception_handler(exc, context):
    response = exception_handler(exc, context)
    
    if response is not None:
        custom_response_data = {
            'success': False,
            'error_code': response.status_code,
            'message': '요청을 처리하는 중 오류가 발생했습니다.',
            'details': response.data
        }
        response.data = custom_response_data
        
    return response

태그: Django DRF python jwt ViewSet

6월 25일 16:14에 게시됨