backend / web frameworks / django / 12_django_vs_drf.md

Django vs Django REST Framework (DRF) Guide

3 interview angles 6 min read source

Django vs Django REST Framework (DRF) Guide

Introduction

Django and Django REST Framework (DRF) are both powerful Python web frameworks, but they serve different purposes and have distinct characteristics. Understanding their differences is crucial for choosing the right tool for your project and for technical interviews.

What is Django?

Django is a high-level Python web framework that follows the Model-View-Template (MVT) architectural pattern. It’s designed for rapid development of web applications with a focus on clean, pragmatic design.

Key Characteristics of Django

  • Full-stack framework: Includes everything needed to build a complete web application
  • Batteries included: Comes with admin interface, ORM, authentication, forms, etc.
  • Template-based: Uses Django templates for rendering HTML
  • Server-side rendering: Generates HTML on the server
  • Monolithic approach: Everything is tightly integrated

What is Django REST Framework (DRF)?

DRF is a powerful and flexible toolkit for building Web APIs. It’s built on top of Django and extends Django’s capabilities specifically for creating RESTful APIs.

Key Characteristics of DRF

  • API-focused: Designed specifically for building APIs
  • JSON/XML responses: Returns structured data instead of HTML
  • Client-server architecture: Separates frontend and backend
  • Stateless: Each request contains all necessary information
  • Extensible: Highly customizable and extensible

Core Differences

1. Purpose and Use Cases

Django:

# Traditional Django view returning HTML
from django.shortcuts import render
from django.http import HttpResponse

def user_profile(request, user_id):
    user = User.objects.get(id=user_id)
    return render(request, 'profile.html', {'user': user})

DRF:

# DRF view returning JSON
from rest_framework.decorators import api_view
from rest_framework.response import Response
from rest_framework import status

@api_view(['GET'])
def user_profile(request, user_id):
    try:
        user = User.objects.get(id=user_id)
        serializer = UserSerializer(user)
        return Response(serializer.data)
    except User.DoesNotExist:
        return Response({'error': 'User not found'}, 
                       status=status.HTTP_404_NOT_FOUND)

2. Response Format

Django Response:

<!-- HTML template rendered by Django -->
<!DOCTYPE html>
<html>
<head>
    <title>User Profile</title>
</head>
<body>
    <h1>{{ user.name }}</h1>
    <p>Email: {{ user.email }}</p>
</body>
</html>

DRF Response:

{
    "id": 1,
    "name": "John Doe",
    "email": "john@example.com",
    "created_at": "2023-01-01T00:00:00Z"
}

3. View Types

Django Views:

# Function-based view
def article_list(request):
    articles = Article.objects.all()
    return render(request, 'articles/list.html', {'articles': articles})

# Class-based view
from django.views.generic import ListView

class ArticleListView(ListView):
    model = Article
    template_name = 'articles/list.html'
    context_object_name = 'articles'

DRF Views:

# Function-based API view
@api_view(['GET', 'POST'])
def article_list(request):
    if request.method == 'GET':
        articles = Article.objects.all()
        serializer = ArticleSerializer(articles, many=True)
        return Response(serializer.data)
    elif request.method == 'POST':
        serializer = ArticleSerializer(data=request.data)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data, status=status.HTTP_201_CREATED)
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

# Class-based API view
from rest_framework import generics

class ArticleListCreateView(generics.ListCreateAPIView):
    queryset = Article.objects.all()
    serializer_class = ArticleSerializer

4. Serialization

Django (Manual):

# Manual serialization in Django
def user_list(request):
    users = User.objects.all()
    user_data = []
    for user in users:
        user_data.append({
            'id': user.id,
            'name': user.name,
            'email': user.email,
            'created_at': user.created_at.isoformat()
        })
    return JsonResponse({'users': user_data})

DRF (Serializers):

# DRF Serializer
from rest_framework import serializers

class UserSerializer(serializers.ModelSerializer):
    class Meta:
        model = User
        fields = ['id', 'name', 'email', 'created_at']

# Usage in view
def user_list(request):
    users = User.objects.all()
    serializer = UserSerializer(users, many=True)
    return Response(serializer.data)

5. Authentication

Django Authentication:

# Django session-based authentication
from django.contrib.auth.decorators import login_required

@login_required
def protected_view(request):
    return render(request, 'protected.html')

DRF Authentication:

# DRF token-based authentication
from rest_framework.authentication import TokenAuthentication
from rest_framework.permissions import IsAuthenticated

class ProtectedAPIView(APIView):
    authentication_classes = [TokenAuthentication]
    permission_classes = [IsAuthenticated]
    
    def get(self, request):
        return Response({'message': 'Authenticated!'})

6. URL Patterns

Django URLs:

# urls.py
from django.urls import path
from . import views

urlpatterns = [
    path('users/', views.user_list, name='user_list'),
    path('users/<int:user_id>/', views.user_detail, name='user_detail'),
]

DRF URLs:

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

router = DefaultRouter()
router.register(r'users', views.UserViewSet)

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

Advanced Features Comparison

1. ViewSets and Routers

DRF ViewSets:

from rest_framework import viewsets

class UserViewSet(viewsets.ModelViewSet):
    queryset = User.objects.all()
    serializer_class = UserSerializer
    
    def get_queryset(self):
        queryset = User.objects.all()
        name = self.request.query_params.get('name', None)
        if name is not None:
            queryset = queryset.filter(name__icontains=name)
        return queryset

Django Equivalent (Manual):

# Would require multiple views and manual URL routing
def user_list(request):
    # List logic
    pass

def user_create(request):
    # Create logic
    pass

def user_detail(request, pk):
    # Retrieve logic
    pass

def user_update(request, pk):
    # Update logic
    pass

def user_delete(request, pk):
    # Delete logic
    pass

2. Filtering and Pagination

DRF Filtering:

from rest_framework.filters import SearchFilter, OrderingFilter
from django_filters.rest_framework import DjangoFilterBackend

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

Django Manual Filtering:

def article_list(request):
    articles = Article.objects.all()
    
    # Manual filtering
    category = request.GET.get('category')
    if category:
        articles = articles.filter(category=category)
    
    # Manual search
    search = request.GET.get('search')
    if search:
        articles = articles.filter(
            Q(title__icontains=search) | Q(content__icontains=search)
        )
    
    # Manual ordering
    ordering = request.GET.get('ordering', '-created_at')
    articles = articles.order_by(ordering)
    
    # Manual pagination
    paginator = Paginator(articles, 10)
    page = request.GET.get('page')
    articles = paginator.get_page(page)
    
    return render(request, 'articles/list.html', {'articles': articles})

3. Permissions

DRF Permissions:

from rest_framework.permissions import IsAuthenticated, IsAdminUser

class ArticleViewSet(viewsets.ModelViewSet):
    queryset = Article.objects.all()
    serializer_class = ArticleSerializer
    
    def get_permissions(self):
        if self.action in ['create', 'update', 'partial_update', 'destroy']:
            permission_classes = [IsAuthenticated]
        else:
            permission_classes = []
        return [permission() for permission in permission_classes]

Django Permissions:

from django.contrib.auth.decorators import login_required, permission_required

@login_required
@permission_required('app.add_article')
def create_article(request):
    # Create logic
    pass

Performance Considerations

Django Performance

  • Template rendering: Can be cached and optimized
  • Database queries: ORM optimization with select_related/prefetch_related
  • Static files: Served efficiently with proper configuration

DRF Performance

  • Serialization overhead: Can be optimized with select_related
  • JSON parsing: Generally faster than HTML rendering
  • Caching: Can cache serialized responses
  • Database queries: Same ORM optimization techniques apply

When to Use Django vs DRF

Use Django When:

  • Building traditional web applications with server-side rendering
  • Need admin interface out of the box
  • Working with forms and user input
  • Building content management systems
  • Team prefers monolithic architecture
  • Need rapid prototyping with minimal frontend complexity

Use DRF When:

  • Building APIs for mobile apps or SPAs
  • Creating microservices
  • Need JSON/XML responses
  • Building headless CMS
  • Working with modern frontend frameworks (React, Vue, Angular)
  • Need API documentation (DRF provides automatic docs)
  • Building RESTful services

Use Both Together:

# Django for admin and traditional views
# DRF for API endpoints
urlpatterns = [
    path('admin/', admin.site.urls),
    path('', include('webapp.urls')),  # Django views
    path('api/', include('api.urls')),  # DRF views
]

Migration Paths

From Django to DRF:

  1. Add DRF to your Django project
  2. Create serializers for your models
  3. Convert views to API views
  4. Update URL patterns
  5. Test API endpoints

From DRF to Django:

  1. Create templates for your data
  2. Convert API views to Django views
  3. Update URL patterns
  4. Handle form processing
  5. Test web pages

Best Practices

Django Best Practices:

  • Use class-based views for complex logic
  • Leverage Django’s built-in security features
  • Use Django’s ORM efficiently
  • Implement proper caching strategies
  • Follow Django’s project structure conventions

DRF Best Practices:

  • Use serializers for data validation
  • Implement proper authentication and permissions
  • Use ViewSets for CRUD operations
  • Implement proper error handling
  • Use pagination for large datasets
  • Document your APIs

Conclusion

Django and DRF are complementary tools that can be used together or separately depending on your project requirements. Django excels at building traditional web applications with server-side rendering, while DRF is specifically designed for building APIs and modern web services.

Understanding the differences between these frameworks is essential for:

  • Choosing the right tool for your project
  • Technical interviews where you might be asked to compare them
  • Building scalable and maintainable applications
  • Making informed architectural decisions

Both frameworks are powerful in their own right, and the choice between them (or using both) depends on your specific use case, team expertise, and project requirements.

Interview angle

  • “What does DRF add to Django?” - serializers for validation and rendering, viewsets and routers for CRUD conventions, authentication and permission classes, pagination, filtering, throttling and browsable docs. Django alone has no API layer.
  • “DRF or FastAPI for a new API?” - FastAPI for type-driven validation, native async and automatic OpenAPI; DRF when you’re already in Django and want the ORM, admin and auth ecosystem. The deciding factor is usually the existing codebase.
  • “Where does DRF get slow?” - nested serializers causing N+1 queries, and serialisation cost on large lists. Fix the queryset first with select_related/prefetch_related, then consider a flatter representation.