API Development
This guide helps you choose the right API approach for your Django backend. The template ships with Django REST Framework by default, but GraphQL and django-ninja are viable alternatives depending on your requirements.
Overview
Your API layer sits between HTTP requests and your service layer (see Service Layer Patterns). The choice of API framework affects:
How you define request/response schemas
Authentication and permission patterns
OpenAPI documentation generation
Async support and performance characteristics
The template’s use_drf option enables Django REST Framework with drf-spectacular for OpenAPI generation. For frontend type safety with DRF, see Type-Safe API Integration.
Django REST Framework (Default)
DRF is the template’s default choice because it’s mature, has the largest ecosystem, and integrates directly with Django’s authentication and ORM.
Why DRF
Mature ecosystem: Authentication backends, pagination, filtering, throttling all built-in or available as packages
Browsable API: Interactive documentation useful during development
Serializers: Declarative request/response validation with ORM integration
drf-spectacular: First-class OpenAPI 3.0 generation for type-safe frontend clients
When to Use
DRF is the right choice for most projects, especially:
CRUD-heavy applications
Projects requiring extensive authentication options (OAuth, JWT, session, token)
Teams familiar with Django patterns
Applications where OpenAPI documentation is important
Basic Pattern
With the services pattern, views become thin orchestration layers:
# {project_slug}/tasks/api/serializers.py
from rest_framework import serializers
class TaskCreateInputSerializer(serializers.Serializer):
title = serializers.CharField(max_length=200)
description = serializers.CharField(required=False)
class TaskSerializer(serializers.Serializer):
id = serializers.IntegerField(read_only=True)
title = serializers.CharField()
description = serializers.CharField()
status = serializers.CharField()
created_at = serializers.DateTimeField()
# {project_slug}/tasks/api/views.py
from rest_framework import status
from rest_framework.response import Response
from rest_framework.views import APIView
from {project_slug}.tasks.services import task_create
from {project_slug}.tasks.selectors import task_list
from .serializers import TaskCreateInputSerializer, TaskSerializer
class TaskListCreateView(APIView):
def get(self, request):
tasks = task_list(fetched_by=request.user)
return Response(TaskSerializer(tasks, many=True).data)
def post(self, request):
serializer = TaskCreateInputSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
task = task_create(
created_by=request.user,
**serializer.validated_data,
)
return Response(TaskSerializer(task).data, status=status.HTTP_201_CREATED)
GraphQL
GraphQL offers a query language that lets clients request exactly the data they need. Consider it when your data relationships are complex or client needs vary significantly.
When to Consider
Complex data graphs: Deeply nested relationships where REST would require multiple round-trips
Diverse clients: Mobile and web apps with different data requirements from the same backend
API aggregation: Unifying multiple data sources behind a single endpoint
Rapid iteration: Frontend teams need flexibility without backend changes for each new view
Trade-offs
Advantage |
Consideration |
|---|---|
Flexible queries |
HTTP caching is harder (POST requests) |
Single endpoint |
N+1 query problems require careful attention (use DataLoader) |
Strong typing |
Learning curve for teams new to GraphQL |
Self-documenting schema |
Tooling less mature than REST ecosystem |
Library Options
- graphene-django
The established choice with Django ORM integration, relay support, and extensive documentation. Sync-only.
- strawberry-graphql
Modern, async-native library with type hints and dataclass-based schema definition. Better fit for async Django deployments.
Adding GraphQL to Your Project
GraphQL is not included in the generated template. To add it:
Install your chosen library (
pip install graphene-djangoorpip install strawberry-graphql[django])Add to
INSTALLED_APPSDefine your schema in each module (e.g.,
{project_slug}/tasks/graphql/schema.py)Mount the GraphQL endpoint in
config/urls.py
Note
GraphQL support as a template option is on the roadmap. See Roadmap: Features Documented But Not Yet Implemented.
django-ninja
django-ninja brings FastAPI’s developer experience to Django: type hints for validation, automatic OpenAPI generation, and native async support.
When to Consider
Async-first: Your application heavily uses async views and you want
use_async=ybenefits throughoutFastAPI familiarity: Team has FastAPI experience and prefers that API style
Pydantic validation: You want Pydantic models for request/response validation
Performance-critical: Async endpoints for I/O-bound operations
Strengths
Native async: Built for async from the ground up
Type-hint validation: Request bodies, query params, and path params validated via type hints
Built-in OpenAPI: Automatic schema generation without additional packages
Familiar syntax: Similar to FastAPI, lower learning curve for those with that background
Trade-offs
Advantage |
Consideration |
|---|---|
Async native |
Smaller ecosystem than DRF |
Type-hint validation |
Less Django ORM integration (no ModelSerializer equivalent) |
FastAPI-like syntax |
Fewer third-party extensions |
Built-in OpenAPI |
Community and documentation smaller |
Basic Pattern
# {project_slug}/tasks/api/routes.py
from ninja import Router, Schema
from typing import List
from {project_slug}.tasks.services import task_create
from {project_slug}.tasks.selectors import task_list
router = Router()
class TaskIn(Schema):
title: str
description: str = ""
class TaskOut(Schema):
id: int
title: str
description: str
status: str
@router.get("/", response=List[TaskOut])
def list_tasks(request):
return task_list(fetched_by=request.user)
@router.post("/", response=TaskOut)
def create_task(request, payload: TaskIn):
return task_create(
created_by=request.user,
title=payload.title,
description=payload.description,
)
Adding django-ninja to Your Project
django-ninja is not included in the generated template. To add it:
Install:
pip install django-ninjaCreate routers in each module
Mount the NinjaAPI in
config/urls.py
# config/urls.py
from ninja import NinjaAPI
api = NinjaAPI()
api.add_router("/tasks/", "myproject.tasks.api.routes.router")
urlpatterns = [
path("api/", api.urls),
# ...
]
Note
django-ninja support as a template option is on the roadmap. See Roadmap: Features Documented But Not Yet Implemented.
Choosing the Right Approach
Use this matrix to guide your decision:
Consideration |
DRF |
GraphQL |
django-ninja |
|---|---|---|---|
Async native |
No (WSGI) |
Strawberry: Yes |
Yes |
Ecosystem size |
Largest |
Medium |
Growing |
OpenAPI generation |
Excellent |
N/A (own schema) |
Built-in |
Django ORM integration |
Deep integration |
Good |
Manual mapping |
Learning curve |
Moderate |
Steeper |
Lower (FastAPI) |
Best for |
Most apps |
Complex graphs |
Async-first |
Recommendation: Start with DRF unless you have a specific reason not to. It’s the safe default that works well for most Django applications. Consider alternatives when:
GraphQL: Your frontend team specifically requests it, or you have genuinely complex data graph requirements
django-ninja: You’re building an async-first application and want that paradigm throughout your API layer
See Also
Type-Safe API Integration — End-to-end type safety with DRF and React
Service Layer Patterns — Where business logic belongs (not in views)
Django REST Framework — Official DRF documentation
graphene-django — GraphQL for Django
Strawberry — Async-native GraphQL
django-ninja — FastAPI-style APIs for Django