Try Live
Add Docs
Rankings
Pricing
Enterprise
Docs
Install
Theme
Install
Docs
Pricing
Enterprise
More...
More...
Try Live
Rankings
Create API Key
Add Docs
Django Modern REST
https://github.com/wemake-services/django-modern-rest
Admin
A modern REST framework for Django that provides type safety and async support, featuring strict
...
Tokens:
57,221
Snippets:
273
Trust Score:
9.3
Update:
1 month ago
Context
Skills
Chat
Benchmark
72.9
Suggestions
Latest
Show doc for...
Code
Info
Show Results
Context Summary (auto-generated)
Raw
Copy
Link
# 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 **method parameters** 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. Components are now defined as method parameters, not class 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]): def post( self, parsed_body: Body[UserCreateInput], parsed_headers: Headers[HeaderModel], ) -> UserModel: # Access typed parsed components directly from parameters assert parsed_headers.consumer == 'my-api' return UserModel( uid=uuid.uuid4(), email=parsed_body.email, age=parsed_body.age, ) def get(self, parsed_query: Query[UserQuery]) -> list[UserModel]: # Query params accessed via parsed_query parameter if 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 as django_path from dmr.routing import Router, path from .controllers import UserController, ProductController # Create router with prefix router = Router( 'api/v1/', [ path('users/', UserController.as_view(), name='users'), path('users/<int:user_id>/', UserController.as_view(), name='user-detail'), path('products/', ProductController.as_view(), name='products'), ], ) # Include in Django urlpatterns urlpatterns = [ django_path(router.prefix, include((router.urls, 'myapp'), namespace='api')), ] ``` ## Path Parameters with Type Validation Parse and validate URL path parameters using the `Path` component as a method parameter 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(Controller[PydanticSerializer]): def get(self, parsed_path: Path[UserPath]) -> dict: # Path params are validated and typed user_id = parsed_path.user_id return {'user_id': user_id, 'name': f'User {user_id}'} def delete(self, parsed_path: Path[UserPath]) -> None: user_id = parsed_path.user_id # Delete user logic here return None # Route: path('users/<int:user_id>/', 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]): @validate( ResponseSpec(TaskModel, status_code=HTTPStatus.CREATED), ResponseSpec(None, status_code=HTTPStatus.NO_CONTENT), ) def post(self, parsed_body: Body[TaskModel]) -> HttpResponse: task = 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]): @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, parsed_body: Body[TaskInput]) -> TaskOutput: # Return model directly - framework serializes it return TaskOutput(id=1, title=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]): @modify( extra_responses=[ ResponseSpec(str, status_code=HTTPStatus.BAD_REQUEST), ], ) def post(self, parsed_body: Body[DivisionInput]) -> float: if 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 parsed_body.numerator / 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]): @modify(error_handler=division_error_handler) def patch(self, parsed_body: Body[TwoNumbers]) -> float: return parsed_body.left / 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]): parsers = (MultiPartParser(),) def post( self, parsed_body: Body[BodyPayload], parsed_file_metadata: FileMetadata[UploadedFiles], ) -> dict: # Access validated file metadata doc_meta = parsed_file_metadata.document # Access actual uploaded files via request.FILES document: UploadedFile = self.request.FILES['document'] return { 'title': 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('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]): def post(self, parsed_body: Body[UserInput]) -> UserOutput: return UserOutput( id=1, email=parsed_body.email, age=parsed_body.age, ) ``` ## Server-Sent Events (SSE) Implement real-time streaming endpoints with typed SSE events using the new SSEController API. ```python import dataclasses from collections.abc import AsyncIterator from dmr.components import Headers from dmr.plugins.msgspec import MsgspecSerializer from dmr.streaming.sse import SSEController, SSEvent @dataclasses.dataclass(frozen=True, slots=True) class NotificationEvent: message: str timestamp: str class HeaderModel(msgspec.Struct): last_event_id: int | None = msgspec.field( default=None, name='Last-Event-ID', ) class NotificationsController(SSEController[MsgspecSerializer]): def get( self, parsed_headers: Headers[HeaderModel], ) -> AsyncIterator[SSEvent[NotificationEvent]]: return self.generate_notifications(parsed_headers) async def generate_notifications( self, parsed_headers: HeaderModel, ) -> 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) # Route: path('notifications/', NotificationsController.as_view(), name='notifications') ``` ## 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]): async def get(self, parsed_query: Query[SearchQuery]) -> SearchResult: # Async database query simulation await asyncio.sleep(0.1) query = parsed_query.q limit = 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 parameters (Body, Query, Headers, Path, Cookies, FileMetadata) in method signatures, 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.