# django-modern-rest Django Modern REST (DMR) is a high-performance REST framework for Django that provides full type safety, async support, and automatic OpenAPI schema generation. Unlike traditional Django REST Framework, DMR leverages Python's modern typing system with support for both Pydantic and msgspec serializers, enabling strict schema validation of both requests and responses at runtime. The framework integrates seamlessly with Django's existing ecosystem while providing blazingly fast performance through optimized JSON parsing. The framework's class-based controller architecture follows a declarative pattern where request components (body, query, headers, path, cookies) are defined as mixins using generic type parameters. This design enables automatic validation, type inference, and documentation generation while maintaining full compatibility with Django's URL routing, middleware, and authentication systems. DMR supports both synchronous and asynchronous endpoints without requiring `sync_to_async` calls, making it ideal for modern async Django applications. ## Controller Definition with Pydantic Create type-safe API controllers by inheriting from `Controller` with a serializer type parameter and component mixins. The controller methods map directly to HTTP methods (get, post, put, patch, delete). ```python import uuid import pydantic from dmr import Body, Controller, Headers, Query from dmr.plugins.pydantic import PydanticSerializer class UserCreateInput(pydantic.BaseModel): email: str age: int class UserModel(UserCreateInput): uid: uuid.UUID class HeaderModel(pydantic.BaseModel): consumer: str = pydantic.Field(alias='X-API-Consumer') class UserQuery(pydantic.BaseModel): include_details: bool = False class UserController( Controller[PydanticSerializer], Body[UserCreateInput], Headers[HeaderModel], Query[UserQuery], ): def post(self) -> UserModel: # Access typed parsed components via self attributes assert self.parsed_headers.consumer == 'my-api' return UserModel( uid=uuid.uuid4(), email=self.parsed_body.email, age=self.parsed_body.age, ) def get(self) -> list[UserModel]: # Query params accessed via self.parsed_query if self.parsed_query.include_details: return self._get_detailed_users() return self._get_basic_users() ``` ## URL Routing with Router Define API routes using the `Router` class and `path` function, which provides faster prefix-based matching than Django's default routing. ```python from django.urls import include, path from dmr.routing import Router, path as dmr_path from .controllers import UserController, ProductController # Create router with prefix router = Router([ dmr_path('users/', UserController.as_view(), name='users'), dmr_path('users//', UserController.as_view(), name='user-detail'), dmr_path('products/', ProductController.as_view(), name='products'), ], prefix='api/v1/') # Include in Django urlpatterns urlpatterns = [ path(router.prefix, include((router.urls, 'myapp'), namespace='api')), ] ``` ## Path Parameters with Type Validation Parse and validate URL path parameters using the `Path` component mixin with a Pydantic model. ```python import pydantic from dmr import Path, Controller from dmr.plugins.pydantic import PydanticSerializer class UserPath(pydantic.BaseModel): user_id: int class UserDetailController( Path[UserPath], Controller[PydanticSerializer], ): def get(self) -> dict: # Path params are validated and typed user_id = self.parsed_path.user_id return {'user_id': user_id, 'name': f'User {user_id}'} def delete(self) -> None: user_id = self.parsed_path.user_id # Delete user logic here return None # Route: path('users//', UserDetailController.as_view()) ``` ## Response Validation with @validate Decorator Use the `@validate` decorator for endpoints returning `HttpResponse` to specify exact response schemas and status codes. ```python from http import HTTPStatus from django.http import HttpResponse import pydantic from dmr import Body, Controller, ResponseSpec, validate from dmr.plugins.pydantic import PydanticSerializer class TaskModel(pydantic.BaseModel): id: int title: str completed: bool class TaskController(Controller[PydanticSerializer], Body[TaskModel]): @validate( ResponseSpec(TaskModel, status_code=HTTPStatus.CREATED), ResponseSpec(None, status_code=HTTPStatus.NO_CONTENT), ) def post(self) -> HttpResponse: task = self.parsed_body if task.title == 'skip': return self.to_response(None, status_code=HTTPStatus.NO_CONTENT) return self.to_response(task, status_code=HTTPStatus.CREATED) ``` ## Response Modification with @modify Decorator Use `@modify` for endpoints returning model objects directly. The framework handles serialization automatically. ```python from http import HTTPStatus from dmr import Controller, Body, modify, ResponseSpec, HeaderSpec from dmr.plugins.pydantic import PydanticSerializer import pydantic class TaskInput(pydantic.BaseModel): title: str class TaskOutput(pydantic.BaseModel): id: int title: str class TaskController(Controller[PydanticSerializer], Body[TaskInput]): @modify( status_code=HTTPStatus.ACCEPTED, headers={'X-Task-Status': HeaderSpec(description='Task processing status')}, extra_responses=[ ResponseSpec(str, status_code=HTTPStatus.BAD_REQUEST), ], ) def post(self) -> TaskOutput: # Return model directly - framework serializes it return TaskOutput(id=1, title=self.parsed_body.title) ``` ## Error Handling with APIError Raise `APIError` to return structured error responses from any point in your endpoint code. ```python from http import HTTPStatus from dmr import Controller, Body, APIError, modify, ResponseSpec from dmr.errors import ErrorType from dmr.plugins.pydantic import PydanticSerializer import pydantic class DivisionInput(pydantic.BaseModel): numerator: float denominator: float class MathController(Controller[PydanticSerializer], Body[DivisionInput]): @modify( extra_responses=[ ResponseSpec(str, status_code=HTTPStatus.BAD_REQUEST), ], ) def post(self) -> float: if self.parsed_body.denominator == 0: raise APIError( self.format_error( 'Division by zero is not allowed', error_type=ErrorType.user_msg, ), status_code=HTTPStatus.BAD_REQUEST, ) return self.parsed_body.numerator / self.parsed_body.denominator ``` ## Custom Error Handler per Endpoint Define custom error handlers for specific exception types at the endpoint level. ```python from http import HTTPStatus from django.http import HttpResponse import pydantic from dmr import Body, Controller, modify from dmr.endpoint import Endpoint from dmr.plugins.pydantic import PydanticSerializer from dmr.serializer import BaseSerializer class TwoNumbers(pydantic.BaseModel): left: int right: int def division_error_handler( endpoint: Endpoint, controller: Controller[BaseSerializer], exc: Exception, ) -> HttpResponse: if isinstance(exc, ZeroDivisionError): return controller.to_error( controller.format_error(str(exc)), status_code=HTTPStatus.BAD_REQUEST, ) raise exc # Re-raise unknown exceptions class MathController(Controller[PydanticSerializer], Body[TwoNumbers]): @modify(error_handler=division_error_handler) def patch(self) -> float: return self.parsed_body.left / self.parsed_body.right ``` ## JWT Authentication Protect endpoints with JWT authentication using built-in sync or async auth classes. ```python from dmr import Controller, modify from dmr.plugins.pydantic import PydanticSerializer from dmr.security.jwt import JWTSyncAuth, JWTAsyncAuth, get_jwt # Sync controller with JWT auth class ProtectedSyncController(Controller[PydanticSerializer]): auth = (JWTSyncAuth(),) def get(self) -> dict: # Access authenticated user user = self.request.user token = get_jwt(self.request) return { 'user_id': str(user.pk), 'token_exp': token.exp.isoformat() if token.exp else None, } # Async controller with JWT auth class ProtectedAsyncController(Controller[PydanticSerializer]): auth = (JWTAsyncAuth(),) async def get(self) -> dict: user = self.request.user return {'user_id': str(user.pk)} # Custom JWT configuration class CustomJWTController(Controller[PydanticSerializer]): auth = (JWTSyncAuth( user_id_field='email', # Lookup user by email instead of pk algorithm='HS512', auth_header='X-Auth-Token', leeway=30, # Allow 30 seconds clock skew ),) def get(self) -> dict: return {'authenticated': True} ``` ## JWT Token Creation for Login Endpoints Create JWT tokens using the built-in token views for login/refresh flows. ```python import datetime as dt from typing_extensions import override from dmr.plugins.pydantic import PydanticSerializer from dmr.security.jwt.views import ( ObtainTokensPayload, ObtainTokensResponse, ObtainTokensSyncController, ) class LoginController( ObtainTokensSyncController[ PydanticSerializer, ObtainTokensPayload, ObtainTokensResponse, ], ): @override def convert_auth_payload( self, payload: ObtainTokensPayload, ) -> ObtainTokensPayload: # Validate credentials here return payload @override def make_api_response(self) -> ObtainTokensResponse: now = dt.datetime.now(dt.UTC) return { 'access_token': self.create_jwt_token( expiration=now + self.jwt_expiration, token_type='access', ), 'refresh_token': self.create_jwt_token( expiration=now + self.jwt_refresh_expiration, token_type='refresh', ), } ``` ## File Upload Handling Handle file uploads with metadata validation using `FileMetadata` component and `MultiPartParser`. ```python from typing import Literal import pydantic from django.core.files.uploadedfile import UploadedFile from dmr import Body, Controller, FileMetadata from dmr.parsers import MultiPartParser from dmr.plugins.pydantic import PydanticSerializer class FileModel(pydantic.BaseModel): content_type: Literal['text/plain', 'application/pdf'] size: int = pydantic.Field(lt=10_000_000) # Max 10MB class UploadedFiles(pydantic.BaseModel): document: FileModel attachment: FileModel class BodyPayload(pydantic.BaseModel): title: str description: str class FileUploadController( Controller[PydanticSerializer], Body[BodyPayload], FileMetadata[UploadedFiles], ): parsers = (MultiPartParser(),) def post(self) -> dict: # Access validated file metadata doc_meta = self.parsed_file_metadata.document # Access actual uploaded files via request.FILES document: UploadedFile = self.request.FILES['document'] return { 'title': self.parsed_body.title, 'document_size': doc_meta.size, 'document_type': doc_meta.content_type, 'filename': document.name, } ``` ## OpenAPI Schema Generation Generate OpenAPI 3.1 specifications automatically from your controllers. ```python from dmr.openapi import OpenAPIConfig, build_schema from dmr.openapi.objects import Contact, License, Server from dmr.routing import Router # Configure OpenAPI metadata config = OpenAPIConfig( title='My API', version='1.0.0', description='A sample API built with django-modern-rest', contact=Contact( name='API Support', email='support@example.com', ), license=License(name='MIT'), servers=[ Server(url='https://api.example.com', description='Production'), Server(url='https://staging-api.example.com', description='Staging'), ], ) # Build schema from router router = Router([...], prefix='api/') schema = build_schema(router, config=config) # Serve schema as JSON endpoint from django.http import JsonResponse def openapi_schema(request): return JsonResponse(schema.to_dict()) ``` ## Msgspec Serializer for Performance Use msgspec serializer for maximum JSON parsing performance with dataclasses. ```python import dataclasses from dmr import Body, Controller from dmr.plugins.msgspec import MsgspecSerializer @dataclasses.dataclass(frozen=True, slots=True) class UserInput: email: str age: int @dataclasses.dataclass(frozen=True, slots=True) class UserOutput: id: int email: str age: int class UserController( Controller[MsgspecSerializer], Body[UserInput], ): def post(self) -> UserOutput: return UserOutput( id=1, email=self.parsed_body.email, age=self.parsed_body.age, ) ``` ## Server-Sent Events (SSE) Implement real-time streaming endpoints with typed SSE events. ```python import dataclasses from collections.abc import AsyncIterator from django.http import HttpRequest from dmr.plugins.msgspec import MsgspecSerializer from dmr.sse import SSEContext, SSEResponse, SSEvent, sse @dataclasses.dataclass(frozen=True, slots=True) class NotificationEvent: message: str timestamp: str async def generate_notifications() -> AsyncIterator[SSEvent[NotificationEvent]]: import asyncio from datetime import datetime for i in range(5): yield SSEvent( NotificationEvent( message=f'Notification {i}', timestamp=datetime.now().isoformat(), ), event='notification', ) await asyncio.sleep(1) @sse(MsgspecSerializer) async def notifications_stream( request: HttpRequest, context: SSEContext, ) -> SSEResponse[SSEvent[NotificationEvent]]: return SSEResponse(generate_notifications()) # Route: path('notifications/', notifications_stream, name='notifications') ``` ## Blueprint Composition Compose multiple blueprints with different HTTP methods into a single controller for complex routing scenarios. ```python from dmr import Blueprint, Controller, Body from dmr.routing import compose_blueprints from dmr.plugins.pydantic import PydanticSerializer import pydantic class CreateInput(pydantic.BaseModel): name: str class UpdateInput(pydantic.BaseModel): name: str active: bool class ItemOutput(pydantic.BaseModel): id: int name: str active: bool class CreateBlueprint(Blueprint[PydanticSerializer], Body[CreateInput]): def post(self) -> ItemOutput: return ItemOutput(id=1, name=self.parsed_body.name, active=True) class UpdateBlueprint(Blueprint[PydanticSerializer], Body[UpdateInput]): def put(self) -> ItemOutput: return ItemOutput( id=1, name=self.parsed_body.name, active=self.parsed_body.active, ) class ReadBlueprint(Blueprint[PydanticSerializer]): def get(self) -> ItemOutput: return ItemOutput(id=1, name='Item', active=True) # Compose blueprints into single controller ItemController = compose_blueprints( CreateBlueprint, UpdateBlueprint, ReadBlueprint, ) # Route: path('items/', ItemController.as_view()) ``` ## Custom Middleware with Response Specs Wrap controllers with custom middleware while maintaining OpenAPI documentation. ```python from collections.abc import Callable from http import HTTPStatus from django.http import HttpRequest, HttpResponse from dmr import Controller, ResponseSpec from dmr.decorators import wrap_middleware from dmr.errors import ErrorModel, format_error from dmr.plugins.pydantic import PydanticSerializer from dmr.response import build_response def rate_limit_middleware( get_response: Callable[[HttpRequest], HttpResponse], ) -> Callable[[HttpRequest], HttpResponse]: def decorator(request: HttpRequest) -> HttpResponse: # Check rate limit (simplified example) if request.headers.get('X-Rate-Limited') == 'true': return build_response( PydanticSerializer, raw_data=format_error('Rate limit exceeded'), status_code=HTTPStatus.TOO_MANY_REQUESTS, ) return get_response(request) return decorator @wrap_middleware( rate_limit_middleware, ResponseSpec( return_type=ErrorModel, status_code=HTTPStatus.TOO_MANY_REQUESTS, ), ) def with_rate_limit(response: HttpResponse) -> HttpResponse: return response @with_rate_limit class RateLimitedController(Controller[PydanticSerializer]): responses = with_rate_limit.responses # Include in OpenAPI spec def post(self) -> dict: return {'message': 'Request processed'} ``` ## Async Controller Example Create fully async controllers that integrate with Django's async views. ```python import asyncio from dmr import Controller, Body, Query from dmr.plugins.pydantic import PydanticSerializer import pydantic class SearchQuery(pydantic.BaseModel): q: str limit: int = 10 class SearchResult(pydantic.BaseModel): items: list[str] total: int class AsyncSearchController( Controller[PydanticSerializer], Query[SearchQuery], ): async def get(self) -> SearchResult: # Async database query simulation await asyncio.sleep(0.1) query = self.parsed_query.q limit = self.parsed_query.limit # Perform async search items = [f'Result for {query} #{i}' for i in range(limit)] return SearchResult(items=items, total=len(items)) ``` ## Summary Django Modern REST provides a comprehensive toolkit for building type-safe REST APIs in Django. The primary use cases include building high-performance JSON APIs with automatic request/response validation, implementing JWT-based authentication flows, generating OpenAPI documentation automatically, and handling file uploads with metadata validation. The framework excels in scenarios requiring strict type safety, async support, and integration with modern Python typing features. Integration patterns typically involve defining Pydantic or msgspec models for request/response schemas, creating Controller classes with component mixins (Body, Query, Headers, Path, Cookies), configuring routes using the Router class, and optionally setting up JWT authentication. For production deployments, the msgspec serializer offers superior performance, while Pydantic provides richer validation features. The framework's decorator-based approach (@validate, @modify) allows fine-grained control over response handling while maintaining full OpenAPI compatibility.