Django REST Framework: Building a Social Platform from Scratch
January 5, 2025
12 min read
DjangoPythonREST APIBackend Development
Django REST Framework: Building a Social Platform from Scratch
Django has been my go-to framework for backend development, and building Coonected taught me invaluable lessons about scaling, security, and API design.
Why Django REST Framework?
For Coonected, I needed:
- Robust authentication & authorization
- Complex relational data models
- RESTful API for React frontend
- Admin panel for content moderation
Django REST Framework (DRF) provided all of this out of the box.
Project Architecture
coonected/
├── Django_Backend/
│ ├── users/
│ ├── posts/
│ ├── connections/
│ ├── notifications/
│ └── api/
│ ├── serializers/
│ ├── views/
│ └── urls.py
└── React_Frontend/
Data Models: The Foundation
# users/models.py from django.contrib.auth.models.AbstractUser import AbstractUser class User(AbstractUser): bio = models.TextField(max_length=500, blank=True) avatar = models.ImageField(upload_to='avatars/', null=True) location = models.CharField(max_length=100, blank=True) website = models.URLField(blank=True) created_at = models.DateTimeField(auto_now_add=True) followers = models.ManyToManyField( 'self', symmetrical=False, related_name='following' ) class Meta: indexes = [ models.Index(fields=['username']), models.Index(fields=['created_at']), ]
Serializers: Data Transformation
# api/serializers/user.py from rest_framework import serializers class UserSerializer(serializers.ModelSerializer): followers_count = serializers.IntegerField( source='followers.count', read_only=True ) following_count = serializers.IntegerField( source='following.count', read_only=True ) is_following = serializers.SerializerMethodField() class Meta: model = User fields = [ 'id', 'username', 'bio', 'avatar', 'followers_count', 'following_count', 'is_following' ] def get_is_following(self, obj): request = self.context.get('request') if request and request.user.is_authenticated: return obj.followers.filter( id=request.user.id ).exists() return False
ViewSets: Clean API Endpoints
# api/views/user.py from rest_framework import viewsets, status from rest_framework.decorators import action from rest_framework.response import Response from rest_framework.permissions import IsAuthenticated class UserViewSet(viewsets.ModelViewSet): queryset = User.objects.all() serializer_class = UserSerializer permission_classes = [IsAuthenticated] @action(detail=True, methods=['post']) def follow(self, request, pk=None): user_to_follow = self.get_object() if user_to_follow == request.user: return Response( {'error': 'Cannot follow yourself'}, status=status.HTTP_400_BAD_REQUEST ) request.user.following.add(user_to_follow) # Create notification Notification.objects.create( user=user_to_follow, type='FOLLOW', actor=request.user ) return Response({'status': 'following'}) @action(detail=True, methods=['post']) def unfollow(self, request, pk=None): user_to_unfollow = self.get_object() request.user.following.remove(user_to_unfollow) return Response({'status': 'unfollowed'})
Custom Permissions
# api/permissions.py from rest_framework import permissions class IsOwnerOrReadOnly(permissions.BasePermission): def has_object_permission(self, request, view, obj): if request.method in permissions.SAFE_METHODS: return True return obj.user == request.user class IsVerifiedUser(permissions.BasePermission): message = 'Please verify your email first.' def has_permission(self, request, view): return request.user.is_verified
Pagination for Performance
# settings.py REST_FRAMEWORK = { 'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination', 'PAGE_SIZE': 20, 'MAX_PAGE_SIZE': 100 } # Custom pagination from rest_framework.pagination import CursorPagination class PostPagination(CursorPagination): page_size = 20 ordering = '-created_at' # Newest first cursor_query_param = 'cursor'
Feed Algorithm
# api/views/feed.py from django.db.models import Q, Count class FeedViewSet(viewsets.ReadOnlyModelViewSet): permission_classes = [IsAuthenticated] pagination_class = PostPagination def get_queryset(self): user = self.request.user # Posts from people you follow + your own posts return Post.objects.filter( Q(user__in=user.following.all()) | Q(user=user) ).select_related( 'user' ).prefetch_related( 'likes', 'comments' ).annotate( likes_count=Count('likes'), comments_count=Count('comments') ).order_by('-created_at')
Query Optimization
Problem: N+1 Queries
# BAD - Causes N+1 queries posts = Post.objects.all() for post in posts: print(post.user.username) # Extra query for each post!
Solution: select_related & prefetch_related
# GOOD - Single query with JOIN posts = Post.objects.select_related('user').all() for post in posts: print(post.user.username) # No extra queries! # For many-to-many posts = Post.objects.prefetch_related('likes', 'comments').all()
Caching with Redis
# settings.py CACHES = { 'default': { 'BACKEND': 'django_redis.cache.RedisCache', 'LOCATION': 'redis://127.0.0.1:6379/1', 'OPTIONS': { 'CLIENT_CLASS': 'django_redis.client.DefaultClient', } } } # views.py from django.core.cache import cache from django.utils.decorators import method_decorator from django.views.decorators.cache import cache_page class TrendingPostsView(APIView): @method_decorator(cache_page(60 * 15)) # 15 minutes def get(self, request): trending = Post.objects.filter( created_at__gte=timezone.now() - timedelta(days=7) ).annotate( engagement=Count('likes') + Count('comments') ).order_by('-engagement')[:10] serializer = PostSerializer(trending, many=True) return Response(serializer.data)
Real-time Notifications with Signals
# notifications/signals.py from django.db.models.signals import post_save from django.dispatch import receiver @receiver(post_save, sender=Comment) def create_comment_notification(sender, instance, created, **kwargs): if created and instance.user != instance.post.user: Notification.objects.create( user=instance.post.user, type='COMMENT', actor=instance.user, post=instance.post, content_object=instance )
File Uploads to Cloud Storage
# settings.py MEDIA_URL = '/media/' # For production: AWS S3 if not DEBUG: DEFAULT_FILE_STORAGE = 'storages.backends.s3boto3.S3Boto3Storage' AWS_STORAGE_BUCKET_NAME = 'coonected-media' # api/views/posts.py from rest_framework.parsers import MultiPartParser, FormParser class PostViewSet(viewsets.ModelViewSet): parser_classes = (MultiPartParser, FormParser) def create(self, request): serializer = PostSerializer( data=request.data, context={'request': request} ) if serializer.is_valid(): serializer.save(user=request.user) return Response( serializer.data, status=status.HTTP_201_CREATED ) return Response( serializer.errors, status=status.HTTP_400_BAD_REQUEST )
Testing the API
# tests/test_api.py from rest_framework.test import APITestCase from django.contrib.auth import get_user_model User = get_user_model() class UserAPITest(APITestCase): def setUp(self): self.user = User.objects.create_user( username='testuser', password='testpass123' ) self.client.force_authenticate(user=self.user) def test_follow_user(self): other_user = User.objects.create_user( username='otheruser', password='pass123' ) url = f'/api/users/{other_user.id}/follow/' response = self.client.post(url) self.assertEqual(response.status_code, 200) self.assertTrue( other_user.followers.filter(id=self.user.id).exists() )
Security Best Practices
1. Rate Limiting
# settings.py REST_FRAMEWORK = { 'DEFAULT_THROTTLE_CLASSES': [ 'rest_framework.throttling.AnonRateThrottle', 'rest_framework.throttling.UserRateThrottle' ], 'DEFAULT_THROTTLE_RATES': { 'anon': '100/day', 'user': '1000/day' } }
2. CORS Configuration
# settings.py CORS_ALLOWED_ORIGINS = [ "https://coonected.com", "https://www.coonected.com", ] CORS_ALLOW_CREDENTIALS = True
3. JWT Authentication
# settings.py REST_FRAMEWORK = { 'DEFAULT_AUTHENTICATION_CLASSES': [ 'rest_framework_simplejwt.authentication.JWTAuthentication', ], } SIMPLE_JWT = { 'ACCESS_TOKEN_LIFETIME': timedelta(minutes=60), 'REFRESH_TOKEN_LIFETIME': timedelta(days=7), }
Deployment Checklist
- DEBUG = False
- SECRET_KEY from environment
- Database on managed service (RDS, DigitalOcean)
- Static files on CDN (S3 + CloudFront)
- HTTPS only (Redirect HTTP)
- Database backups automated
- Monitoring (Sentry for errors)
- Logging (CloudWatch, Papertrail)
Performance Results
- 500+ concurrent users handled smoothly
- <100ms API response time (90th percentile)
- 99.95% uptime over 6 months
Key Takeaways
- Use select_related/prefetch_related - Avoid N+1 queries
- Cache aggressively - Redis is your friend
- Index your databases - Especially foreign keys and timestamps
- Test your APIs - Don't ship broken endpoints
- Monitor everything - You can't fix what you can't see
Building with Django? I'd love to hear about your experience! Check out Coonected or connect with me on LinkedIn.
More Projects:
Let's Build Something Great
I'm open to freelance, full-time, or collaboration opportunities.