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:
- Add DRF to your Django project
- Create serializers for your models
- Convert views to API views
- Update URL patterns
- Test API endpoints
From DRF to Django:
- Create templates for your data
- Convert API views to Django views
- Update URL patterns
- Handle form processing
- 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.