Django Client Generator
Auto-generate type-safe, production-ready API clients from OpenAPI 3.0/3.1 specifications
The django_client module generates TypeScript, Python, Go, and Protocol Buffer clients optimized for Django REST Framework applications. Unlike generic OpenAPI generators, it produces clean, framework-specific code for modern ecosystems.
What It Generates
The django_client module generates clients for multiple languages and protocols:
- TypeScript - Type-safe client with Zod validation, SWR hooks, universal fetchers
- Python - Async Pydantic 2 client with full type hints
- Go - Typed HTTP client with structs and interfaces
- Protocol Buffers - Proto3 definitions for gRPC services
TypeScript Client Structure
frontend/src/api/generated/
├── cfg__accounts/ # Tag-based folders
│ ├── client.ts # API client class
│ ├── models.ts # TypeScript interfaces
│ ├── index.ts # Exports
│ └── _utils/ # Universal helpers
│ ├── fetchers/ # Typed fetch functions
│ │ └── accounts.ts # Works in Server Components, Client Components, React Native
│ ├── hooks/ # SWR React hooks
│ │ └── accounts.ts # Client-side data fetching with caching
│ └── schemas/ # Zod validation schemas
│ ├── User.schema.ts # Runtime type validation
│ └── UserRequest.schema.ts
│
├── cfg__payments/
│ └── ... # Same structure for each tag
│
├── client.ts # Main client
├── schema.ts # All Zod schemas
├── enums.ts # Enums
├── errors.ts # Error handling
├── http.ts # HTTP layer
├── logger.ts # Logging
├── retry.ts # Retry logic
├── storage.ts # Storage utilities
└── api-instance.ts # Singleton instancePython Client Structure
backend/api_client/
├── __init__.py
├── client.py # Main async client
├── models/ # Pydantic 2 models
│ ├── accounts.py # User, UserRequest models
│ └── payments.py # Payment models
└── subclients/ # Sub-clients by tag
├── accounts.py # client.accounts.*
└── payments.py # client.payments.*Protocol Buffers Structure
openapi/clients/proto/
├── profiles/ # Group name
│ ├── api__profiles/ # Service folder
│ │ ├── messages.proto # Message definitions (models, enums)
│ │ ├── service.proto # gRPC service definitions
│ │ ├── messages_pb2.py # Compiled Python messages
│ │ ├── messages_pb2_grpc.py # Empty (no services in messages)
│ │ ├── service_pb2.py # Request/Response messages
│ │ └── service_pb2_grpc.py # gRPC client stubs & server servicers
│ └── README.md # Compilation guide
│
├── trading/ # Another group
│ └── api__trading/
│ └── ... # Same structure
│
└── cfg/ # Large group (21 services)
├── accounts/
├── payments/
├── knowbase/
└── ... # One folder per serviceCompilation Required
Proto files are source files that must be compiled with protoc before use. Each group includes a README.md with compilation commands for Python, Go, TypeScript, C++, Java, and more.
Key Features
🎯 Universal Fetch Functions
Typed functions that work in any JavaScript environment:
// Generated: cfg__accounts/_utils/fetchers/accounts.ts
export async function getUsers(
params?: { page?: number; page_size?: number },
client?: API
): Promise<PaginatedUserList> {
const api = client || getAPIInstance()
const response = await api.accounts.list(params)
return PaginatedUserListSchema.parse(response)
}Works in:
- ✅ Next.js Server Components (async/await)
- ✅ Next.js Client Components
- ✅ React Native mobile apps
- ✅ Node.js backend services
- ✅ Remix loaders/actions
⚛️ React SWR Hooks
Client-side hooks with caching and revalidation:
// Generated: cfg__accounts/_utils/hooks/accounts.ts
export function useUsers(params?: { page?: number }) {
return useSWR(
params ? ['users', params] : 'users',
() => getUsers(params)
)
}
// Usage
function UsersTable() {
const { data, error, isLoading, mutate } = useUsers({ page: 1 })
// Auto-caching, revalidation, error retry
}✅ Zod Validation Schemas
Runtime type validation for all models:
// Generated: cfg__accounts/_utils/schemas/User.schema.ts
export const UserSchema = z.object({
id: z.number(),
username: z.string().min(1).max(150),
email: z.email(),
created_at: z.string()
})
// Runtime validation
const user = UserSchema.parse(apiResponse) // ✅ Type-safe🔄 Request/Response Split
Automatically detects and separates request/response models:
// Response model (includes read-only fields)
interface User {
id: number // ✅ read-only
username: string
email: string
created_at: string // ✅ read-only
}
// Request model (excludes read-only fields)
interface UserRequest {
username: string
email: string
password: string // ✅ write-only
}
// Patch model (all fields optional)
interface PatchedUserRequest {
username?: string
email?: string
password?: string
}🏗️ IR Layer (Intermediate Representation)
The core innovation that makes it all work:
OpenAPI 3.0/3.1 → Parser → IR Layer → Generators → TypeScript/Python
IR Layer benefits:
✅ Version-agnostic (handles both OpenAPI 3.0 and 3.1)
✅ Language-agnostic (same IR for all generators)
✅ Fully typed with Pydantic 2
✅ Normalizes nullable fields, enums, formatsThe IR layer decouples parsing from generation, making it easy to add new languages without changing parsers.
🎛️ Groups (API Organization)
Organize large APIs into logical groups:
# Django-CFG configuration
openapi_client: OpenAPIClientConfig = OpenAPIClientConfig(
enabled=True,
generate_package_files=True,
generate_zod_schemas=True,
generate_fetchers=True,
generate_swr_hooks=True,
api_prefix="api",
output_dir="openapi",
drf_title="My App API",
drf_description="Complete API documentation",
drf_version="1.0.0",
groups=[
OpenAPIGroupConfig(
name="core",
apps=["users", "accounts"],
title="Core API",
description="User management and authentication",
version="1.0.0",
),
OpenAPIGroupConfig(
name="billing",
apps=["payments", "subscriptions"],
title="Billing API",
description="Payment processing",
version="1.0.0",
),
],
)Benefits:
- Separate clients for different teams/features
- Smaller bundle sizes (import only what you need)
- Clear API boundaries
- Independent versioning
How It Works
1. OpenAPI Parsing
OpenAPI YAML/JSON → openapi-pydantic → Validated specSupports both OpenAPI 3.0 and 3.1 with full validation.
2. IR Transformation
Parsed spec → IR Layer
├── IRSchemaObject (type definitions)
├── IROperationObject (API operations)
├── IRParameterObject (parameters)
└── IRContext (validation & relationships)The IR layer normalizes OpenAPI into a version-agnostic, language-agnostic representation.
3. Code Generation
IR Context → Universal Builders → Language Generators
Universal Builders:
├── ParamsBuilder (eliminates parameter duplication)
├── TypeScriptValidator (validates before compilation)
└── BaseGenerator (common utilities)
Language Generators:
├── TypeScript → fetchers + hooks + schemas
└── Python → async client + Pydantic models4. Validation & Output
Generated code → TypeScriptValidator → File system
Validation catches:
❌ Required params after optional
❌ Required fields in optional objects
❌ Invalid type definitionsReal-World Example
Next.js Server Component
// app/users/page.tsx
import { getUsers } from '@/api/generated/cfg__accounts/_utils/fetchers/accounts'
export default async function UsersPage() {
// Type-safe server-side fetching
const users = await getUsers({ page: 1, page_size: 20 })
return (
<div>
<h1>Users ({users.count})</h1>
<ul>
{users.results.map(user => (
<div key={user.id}>
{user.username} - {user.email}
</div>
))}
</ul>
</div>
)
}React Client Component
'use client'
import { useUsers } from '@/api/generated/cfg__accounts/_utils/hooks/accounts'
export default function UsersTable() {
const { data, error, isLoading } = useUsers({ page: 1 })
if (isLoading) return <Spinner />
if (error) return <Error error={error} />
return <Table data={data.results} />
}Python Async Client
from api_client import APIClient
async def main():
client = APIClient(base_url="https://api.example.com")
# List users
users = await client.accounts.list(page=1, page_size=20)
for user in users.results:
print(f"{user.username} - {user.email}")
# Create user
new_user = await client.accounts.create(data={
"username": "alice",
"email": "[email protected]",
"password": "secret123"
})Why Use This?
Problem: Generic Generators Don’t Fit Django
Generic OpenAPI generators (openapi-generator, swagger-codegen) produce:
- ❌ Bloated code with unnecessary dependencies
- ❌ Poor TypeScript types (excessive
anyusage) - ❌ No React integration (no hooks, no SWR)
- ❌ Incompatible with Next.js Server Components
- ❌ No validation or error prevention
Solution: Purpose-Built for Django + Modern JS
django_client is specifically designed for:
- ✅ Django REST Framework + drf-spectacular
- ✅ Next.js 13+ (App Router, Server Components)
- ✅ React with SWR for data fetching
- ✅ React Native mobile apps
- ✅ Pydantic 2 for Python clients
- ✅ Full type safety end-to-end
Advanced Features
✅ File Uploads
Type-safe multipart/form-data handling:
interface DocumentRequest {
title: string
file: File | Blob // ✅ Binary field
is_public: boolean
}
await uploadDocument({
title: "Doc",
file: fileInput.files[0] // ✅ Type-checked
})✅ Custom Actions (Django ViewSet)
Supports Django @action decorator:
# Django
class UserViewSet(viewsets.ModelViewSet):
@action(detail=False, methods=['get'])
def active(self, request):
...
# Generated TypeScript
await client.users.active() // ✅ Available
# Generated Python
await client.users.active() // ✅ Available✅ Archive System
Maintains version history of all generated clients:
openapi/archive/
├── 2025-01-15_10-00-00/
│ ├── typescript/
│ └── python/
├── 2025-01-15_11-00-00/
│ └── ...Benefits:
- Compare changes between versions
- Rollback if needed
- Track API evolution
- Debug client issues
Configuration
Basic (Django-CFG)
from django_cfg import OpenAPIClientConfig, OpenAPIGroupConfig
openapi_client = OpenAPIClientConfig(
enabled=True,
generate_package_files=True,
generate_zod_schemas=True,
generate_fetchers=True,
generate_swr_hooks=True,
api_prefix="api",
output_dir="openapi",
groups=[
OpenAPIGroupConfig(
name="core",
apps=["users", "accounts"],
title="Core API"
),
],
)CLI Commands
# Generate all groups from Django-CFG
python manage.py generate_api
# Generate single TypeScript client
python manage.py generate_ts_client \
--openapi-schema openapi.yaml \
--output frontend/src/api/generated
# Generate single Python client
python manage.py generate_python_client \
--openapi-schema openapi.yaml \
--output backend/api_clientPerformance
Optimized for large APIs:
Benchmark (300 operations):
- Parsing: ~500ms
- IR Transformation: ~200ms
- TypeScript Generation: ~1.5s
- Python Generation: ~800ms
- Total: ~3 seconds
Features:
- ✅ Parallel generation (TypeScript + Python concurrently)
- ✅ Template caching (Jinja2 compiled once)
- ✅ Lazy loading (IR schemas loaded on demand)
Key Concepts
1. IR Layer
The Intermediate Representation layer is the core innovation:
class IRSchemaObject:
"""Type-safe schema representation"""
name: str # "User"
type: str # "object", "string", "array"
properties: dict # Nested properties
required: list[str] # Required fields
nullable: bool # Normalized from both 3.0 and 3.1
@property
def typescript_type(self) -> str:
"""Get TypeScript type"""
@property
def python_type(self) -> str:
"""Get Python type"""2. Universal Builders
Eliminates code duplication:
ParamsBuilder - Handles parameter logic for ALL generators:
builder = ParamsBuilder(context, base)
params = builder.build_params_structure(operation)
# Returns:
# - Function signature
# - API call arguments
# - SWR key
# - Return typeBenefits:
- ✅ Single source of truth
- ✅ Reduced code by 40%
- ✅ Consistent behavior
3. Request/Response Split
Django REST Framework serializers serve both input and output:
# DRF Serializer
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ['id', 'username', 'email', 'created_at']
read_only_fields = ['id', 'created_at']Generated:
User(response) - includesid,created_atUserRequest(request) - excludesid,created_atPatchedUserRequest(patch) - all fields optional
Next Steps
- Getting Started - Installation and setup (10 minutes)
- CLI Commands - Complete CLI reference for generation and validation
- Examples - Real-world usage patterns
- Configuration Guide - Advanced configuration
Related Documentation
- API Generation Overview - High-level overview
- Group Configuration - Organizing APIs with groups
- CLI Usage - Management commands