-
Notifications
You must be signed in to change notification settings - Fork 20
Development Coding Standards Backend
This document provides guidelines for API development in the EduLite platform. Following these standards helps create consistent, well-documented APIs that are easy to test and maintain.
Follow consistent URL patterns:
/api/users/ # GET list, POST create
/api/users/{id}/ # GET detail, PUT update, DELETE
/api/users/{id}/profile/ # GET nested resource
/api/friend-requests/send/ # POST action endpoint
/api/friend-requests/{id}/accept/ # POST state change
- GET: Retrieve data (never modify)
- POST: Create resources or trigger actions
- PUT: Full update (all fields required)
- PATCH: Partial update (only changed fields)
- DELETE: Remove resources
{
"id": 123,
"username": "student1",
"email": "student@example.com"
}{
"detail": "User not found",
"field_errors": {
"username": ["This field is required"]
}
}{
"count": 150,
"next": "http://api.edulite.org/users/?page=2",
"previous": null,
"results": [...]
}Interactive Testing with Swagger UI (/swagger/)
- Test endpoints directly in the browser
- No need for Postman or curl commands
- Automatic authentication handling
- Try different parameters and see responses instantly
Beautiful Documentation with ReDoc (/redoc/)
- Professional API documentation
- Searchable and organized by tags
- Perfect for sharing with frontend developers
- Auto-generated from your code
Import drf-spectacular utilities for documentation:
from drf_spectacular.utils import (
extend_schema,
inline_serializer,
OpenApiResponse,
OpenApiParameter,
OpenApiTypes,
OpenApiExample
)Start simple and add detail as needed:
# Minimal documentation (good start)
@extend_schema(
summary="Send friend request",
description="Send a friend request to another user",
tags=['Friend Requests']
)
def post(self, request):
# Implementation
# Better documentation with examples
@extend_schema(
summary="Send friend request",
description="Send a friend request to another user. Validates friendship status and prevents duplicates.",
request=SendFriendRequestSerializer,
responses={
201: "Friend request sent successfully",
400: "Invalid request",
404: "User not found"
},
tags=['Friend Requests']
)
def post(self, request):
# Implementation
# Best documentation with full examples
@extend_schema(
summary="Send friend request",
description="Send a friend request to another user. The system prevents duplicate requests and self-requests.",
request=inline_serializer(
name='SendFriendRequest',
fields={
'receiver_username': serializers.CharField(help_text="Username to send request to"),
'message': serializers.CharField(required=False, help_text="Optional message")
}
),
responses={
201: OpenApiResponse(
description="Request sent successfully",
response=inline_serializer(
name='FriendRequestResponse',
fields={
'detail': serializers.CharField(),
'request_id': serializers.IntegerField(),
}
)
),
400: OpenApiResponse(description="Invalid request (duplicate, self-request, etc.)"),
404: OpenApiResponse(description="User not found"),
},
examples=[
OpenApiExample(
'Success Example',
value={'detail': 'Friend request sent', 'request_id': 123},
response_only=True
),
OpenApiExample(
'Request with Message',
value={'receiver_username': 'john_doe', 'message': 'Hey, let\'s study together!'},
request_only=True
)
],
tags=['Friend Requests']
)
def post(self, request):
# ImplementationEssential (always include):
- Summary: Short description (5-10 words)
- Description: What the endpoint does
- Tags: For grouping in Swagger/ReDoc
Helpful (add when relevant):
- Request schema: Show expected input format
- Response examples: Help developers understand output
- Error responses: Common failure scenarios
- Parameters: Query strings, path variables
Advanced (for complex endpoints):
- Multiple examples: Different use cases
- Detailed field descriptions: Complex data structures
- Authentication notes: Special permission requirements
Each app must have a base view class:
class UsersAppBaseAPIView(APIView):
"""Base API view for users app."""
permission_classes = [IsAuthenticated]
def get_serializer_context(self):
"""Include request context for hyperlinked serializers."""
return {"request": self.request}# users/views.py
# --- User CRUD Operations ---
class UserListView(UsersAppBaseAPIView):
"""List users with pagination."""
@extend_schema(
summary="List all users",
description="Returns paginated list of users",
# ... full documentation
)
def get(self, request):
# Implementation with select_related/prefetch_related
pass
# --- Friend Request Management ---
class SendFriendRequestView(UsersAppBaseAPIView):
"""Handle friend request sending."""
@extend_schema(
# ... documentation
)
def post(self, request):
# Thin view - delegates to business logic
from .logic.friend_requests import send_friend_request
success, message = send_friend_request(
request.user,
request.data.get('receiver_username')
)
status_code = 201 if success else 400
return Response({"message": message}, status=status_code)Complex logic must be extracted to separate modules:
# users/logic/friend_requests.py
def send_friend_request(sender: User, receiver_username: str) -> Tuple[bool, str]:
"""
Send friend request with validation.
Args:
sender: User sending request
receiver_username: Target username
Returns:
(success, message) tuple
"""
# Validation and business logic
# Return standardized responseapp/tests/
├── test_views.py # API endpoint tests
├── test_serializers.py # Serialization tests
├── test_permissions.py # Permission tests
└── test_logic.py # Business logic tests
All APIs must include tests for:
- Success responses (2xx)
- Validation errors (400)
- Authentication (401)
- Permission denial (403)
- Not found (404)
- Edge cases
Example:
class SendFriendRequestViewTest(TestCase):
def setUp(self):
self.client = APIClient()
self.user = User.objects.create_user('testuser')
self.client.force_authenticate(user=self.user)
def test_send_request_success(self):
response = self.client.post('/api/friend-requests/send/', {
'receiver_username': 'otheruser'
})
self.assertEqual(response.status_code, 201)
def test_send_request_to_self(self):
response = self.client.post('/api/friend-requests/send/', {
'receiver_username': 'testuser'
})
self.assertEqual(response.status_code, 400)Always use select_related and prefetch_related:
# Bad: N+1 queries
users = User.objects.all()
# Good: Single query
users = User.objects.select_related('profile').prefetch_related('groups')All list endpoints must implement pagination:
class UserListView(APIView):
def get(self, request):
queryset = User.objects.all()
paginator = PageNumberPagination()
paginator.page_size = 10
page = paginator.paginate_queryset(queryset, request)
serializer = UserSerializer(page, many=True)
return paginator.get_paginated_response(serializer.data)All endpoints except registration/login require authentication:
permission_classes = [IsAuthenticated]Validate all input in serializers:
class UserRegistrationSerializer(serializers.ModelSerializer):
password = serializers.CharField(min_length=8, write_only=True)
def validate_username(self, value):
if len(value) < 3:
raise serializers.ValidationError("Username too short")
return valueUse object-level permissions for resource access:
class IsOwnerOrReadOnly(BasePermission):
def has_object_permission(self, request, view, obj):
if request.method in SAFE_METHODS:
return True
return obj.user == request.user# users/views.py
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status
from drf_spectacular.utils import extend_schema, OpenApiResponse, inline_serializer
from .serializers import UserSerializer
class UsersAppBaseAPIView(APIView):
"""Base view for users app."""
permission_classes = [IsAuthenticated]
@extend_schema(tags=['Users'])
class UserProfileView(UsersAppBaseAPIView):
"""User profile operations."""
@extend_schema(
summary="Get user profile",
description="Retrieve detailed profile information for a user",
responses={
200: UserSerializer,
404: OpenApiResponse(description="User not found")
}
)
def get(self, request, pk):
user = get_object_or_404(
User.objects.select_related('profile'),
pk=pk
)
serializer = UserSerializer(user)
return Response(serializer.data)
@extend_schema(
summary="Update user profile",
description="Update user profile information",
request=UserSerializer,
responses={
200: UserSerializer,
400: OpenApiResponse(description="Validation error"),
403: OpenApiResponse(description="Permission denied")
}
)
def patch(self, request, pk):
user = get_object_or_404(User, pk=pk)
self.check_object_permissions(request, user)
serializer = UserSerializer(user, data=request.data, partial=True)
if serializer.is_valid():
serializer.save()
return Response(serializer.data)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)Minimum Requirements:
- Views have @extend_schema decorators with summary and description
- Endpoints appear correctly in
/swagger/and/redoc/ - Tags are used for logical grouping
Quality Improvements:
- Request/response schemas documented
- Common error cases included (400, 404)
- At least one example provided for complex endpoints
- Query parameters documented if present
- Database queries use select_related/prefetch_related where appropriate
- List endpoints implement pagination
- Authentication and permissions configured
- Input validation in serializers
- Complex logic extracted to separate functions
- Tests cover main success and error paths
- Faster Development: Test APIs directly in Swagger without writing client code
- Clear Documentation: Understand endpoints without reading implementation
- Consistent Patterns: Familiar structure across all APIs
- Better Collaboration: Frontend developers can work independently with clear API docs
- Reduced Bugs: Examples and schemas catch issues early
- Professional Image: ReDoc provides beautiful public documentation
- Reliable APIs: Well-tested and documented endpoints
- Better Performance: Optimized queries and pagination
- Consistent Experience: Predictable API behavior
-
View existing documentation: Visit
/swagger/or/redoc/on your local server - Start simple: Add basic @extend_schema decorators to your views
- Iterate: Add more detail as endpoints become more complex
- Test in Swagger: Use the interactive UI to verify your endpoints work
- drf-spectacular documentation
- Django REST Framework
- EduLite API Examples - See our existing implementations
These guidelines help us build better APIs. Questions or suggestions? Open a discussion on GitHub.