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

  1. Use select_related/prefetch_related - Avoid N+1 queries
  2. Cache aggressively - Redis is your friend
  3. Index your databases - Especially foreign keys and timestamps
  4. Test your APIs - Don't ship broken endpoints
  5. 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.