Skip to content

Development Coding Standards Backend

Mathew Storm edited this page Sep 15, 2025 · 4 revisions

Backend API Standards

Purpose

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.

API Design Standards

RESTful Endpoints

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

HTTP Methods

  • 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

Response Formats

Success Response (200/201)

{
    "id": 123,
    "username": "student1",
    "email": "student@example.com"
}

Error Response (400/404/500)

{
    "detail": "User not found",
    "field_errors": {
        "username": ["This field is required"]
    }
}

Paginated Response

{
    "count": 150,
    "next": "http://api.edulite.org/users/?page=2",
    "previous": null,
    "results": [...]
}

OpenAPI Documentation

Why Document Your APIs?

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

Required Imports

Import drf-spectacular utilities for documentation:

from drf_spectacular.utils import (
    extend_schema,
    inline_serializer,
    OpenApiResponse,
    OpenApiParameter,
    OpenApiTypes,
    OpenApiExample
)

Basic Documentation Pattern

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):
    # Implementation

Recommended Documentation Elements

Essential (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

Code Organization

Base View Pattern

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}

View Structure

# 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)

Business Logic Extraction

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 response

Testing Requirements

Test Organization

app/tests/
├── test_views.py         # API endpoint tests
├── test_serializers.py   # Serialization tests
├── test_permissions.py   # Permission tests
└── test_logic.py         # Business logic tests

Required Test Coverage

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)

Performance Guidelines

Database Optimization

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')

Pagination

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)

Security Standards

Authentication

All endpoints except registration/login require authentication:

permission_classes = [IsAuthenticated]

Input Validation

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 value

Permissions

Use 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

Complete Example Implementation

# 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)

Pull Request Guidelines

API Documentation Checklist

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

Code Quality Checklist

  • 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

Benefits of Following These Standards

For Developers

  • 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

For the Project

  • 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

For Users

  • Reliable APIs: Well-tested and documented endpoints
  • Better Performance: Optimized queries and pagination
  • Consistent Experience: Predictable API behavior

Getting Started

  1. View existing documentation: Visit /swagger/ or /redoc/ on your local server
  2. Start simple: Add basic @extend_schema decorators to your views
  3. Iterate: Add more detail as endpoints become more complex
  4. Test in Swagger: Use the interactive UI to verify your endpoints work

References


These guidelines help us build better APIs. Questions or suggestions? Open a discussion on GitHub.

Clone this wiki locally