API Client Generation
Automatically generate type-safe TypeScript clients from your Django APIs.
Overview
Django-CFG can automatically generate TypeScript API clients from your Django REST Framework APIs, copy them to your Next.js project, and build your admin in one command:
python manage.py generate_clients --typescriptThis generates:
- ✅ Type-safe TypeScript clients
- ✅ Request/response type definitions
- ✅ Authentication handling
- ✅ Error handling
- ✅ Always auto-copied to Next.js project
- ✅ Next.js build prompts for confirmation
Quick Start
1. Configure Next.js Admin
api/config.py
from django_cfg import DjangoConfig, NextJsAdminConfig
config = DjangoConfig(
project_name="My Project",
nextjs_admin=NextJsAdminConfig(
project_path="../django_admin",
),
)2. Generate Clients
python manage.py generate_clients --typescript3. Use in Next.js
src/pages/dashboard.tsx
import { ProfilesClient } from '@/api/generated/profiles/client';
export default function Dashboard() {
const client = new ProfilesClient();
const profile = await client.getProfile();
return <div>Welcome, {profile.name}!</div>;
}Command Options
Basic Usage
# Generate TypeScript clients
python manage.py generate_clients --typescript
# Generate Python clients
python manage.py generate_clients --python
# Generate both
python manage.py generate_clients --typescript --python
# Generate all supported languages
python manage.py generate_clients --allAdvanced Options
# Skip building Next.js (no confirmation prompt)
python manage.py generate_clients --typescript --no-build
# Verbose output
python manage.py generate_clients --typescript --verbose
# Dry run (show what would be generated)
python manage.py generate_clients --typescript --dry-runGenerated Structure
Output Directory
django_project/
├── openapi/
│ ├── schema.json # OpenAPI 3.0 schema
│ └── clients/
│ ├── typescript/ # TypeScript clients
│ │ ├── profiles/
│ │ │ ├── client.ts # API client
│ │ │ ├── types.ts # Type definitions
│ │ │ └── http.ts # HTTP utilities
│ │ └── trading/
│ │ └── ...
│ │
│ ├── python/ # Python clients
│ │ └── ...
│ │
│ └── go/ # Go clients
│ └── ...Next.js Integration
If NextJsAdminConfig is configured:
django_admin/
└── apps/
└── admin/
└── src/
└── api/
└── generated/ # Auto-copied here
├── profiles/
├── trading/
└── ...TypeScript Client Usage
Basic API Calls
src/lib/api.ts
import { ProfilesClient } from '@/api/generated/profiles/client';
import { Profile } from '@/api/generated/profiles/types';
// Create client
const client = new ProfilesClient({
baseURL: '/api', // Django API base URL
});
// GET request
const profile: Profile = await client.getProfile();
// POST request
const newProfile = await client.createProfile({
name: 'John Doe',
email: '[email protected]',
});
// PUT request
const updated = await client.updateProfile(profile.id, {
name: 'Jane Doe',
});
// DELETE request
await client.deleteProfile(profile.id);With Authentication
src/lib/authenticated-client.ts
import { ProfilesClient } from '@/api/generated/profiles/client';
function createAuthenticatedClient() {
// Get token from localStorage (injected by Django)
const token = localStorage.getItem('auth_token');
return new ProfilesClient({
baseURL: '/api',
headers: {
'Authorization': token ? `Bearer ${token}` : '',
'Content-Type': 'application/json',
},
});
}
// Usage
const client = createAuthenticatedClient();
const profile = await client.getProfile();Error Handling
src/lib/api-with-error-handling.ts
import { ProfilesClient } from '@/api/generated/profiles/client';
async function safeApiCall<T>(
apiCall: () => Promise<T>
): Promise<{ data: T | null; error: Error | null }> {
try {
const data = await apiCall();
return { data, error: null };
} catch (error) {
console.error('API call failed:', error);
return { data: null, error: error as Error };
}
}
// Usage
const client = createAuthenticatedClient();
const { data, error } = await safeApiCall(() => client.getProfile());
if (error) {
console.error('Failed to load profile:', error);
} else {
console.log('Profile:', data);
}React Hook Pattern
src/hooks/useProfile.ts
import { useEffect, useState } from 'react';
import { createAuthenticatedClient } from '@/lib/api';
import { Profile } from '@/api/generated/profiles/types';
export function useProfile() {
const [profile, setProfile] = useState<Profile | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<Error | null>(null);
useEffect(() => {
const client = createAuthenticatedClient();
client.getProfile()
.then(setProfile)
.catch(setError)
.finally(() => setLoading(false));
}, []);
return { profile, loading, error };
}
// Usage in component
function Dashboard() {
const { profile, loading, error } = useProfile();
if (loading) return <div>Loading...</div>;
if (error) return <div>Error: {error.message}</div>;
return <div>Welcome, {profile?.name}!</div>;
}Type Definitions
Generated Types
Generated: profiles/types.ts
// Automatically generated from Django models
export interface Profile {
id: number;
name: string;
email: string;
bio: string | null;
avatar: string | null;
created_at: string;
updated_at: string;
}
export interface ProfileCreate {
name: string;
email: string;
bio?: string;
avatar?: string;
}
export interface ProfileUpdate {
name?: string;
email?: string;
bio?: string;
avatar?: string;
}
export interface ProfileList {
count: number;
next: string | null;
previous: string | null;
results: Profile[];
}Using Types
src/components/ProfileCard.tsx
import { Profile } from '@/api/generated/profiles/types';
interface ProfileCardProps {
profile: Profile; // Type-safe!
}
export function ProfileCard({ profile }: ProfileCardProps) {
return (
<div>
<h2>{profile.name}</h2>
<p>{profile.email}</p>
{profile.bio && <p>{profile.bio}</p>}
</div>
);
}Advanced Patterns
Request Interceptors
src/lib/api-client.ts
import { ProfilesClient } from '@/api/generated/profiles/client';
class AuthenticatedProfilesClient extends ProfilesClient {
constructor() {
super({
baseURL: '/api',
});
}
// Override request method to add auth
async request<T>(config: RequestConfig): Promise<T> {
const token = localStorage.getItem('auth_token');
return super.request({
...config,
headers: {
...config.headers,
'Authorization': token ? `Bearer ${token}` : '',
},
});
}
}
const client = new AuthenticatedProfilesClient();Response Interceptors
src/lib/api-with-interceptors.ts
import { ProfilesClient } from '@/api/generated/profiles/client';
class InterceptedClient extends ProfilesClient {
async request<T>(config: RequestConfig): Promise<T> {
try {
const response = await super.request<T>(config);
// Log successful requests
console.log(`✅ ${config.method} ${config.url}`, response);
return response;
} catch (error) {
// Handle errors globally
console.error(`❌ ${config.method} ${config.url}`, error);
// Refresh token if expired
if (error.status === 401) {
await this.refreshToken();
// Retry request
return super.request<T>(config);
}
throw error;
}
}
private async refreshToken() {
const refresh = localStorage.getItem('refresh_token');
// ... refresh logic
}
}Caching Layer
src/lib/cached-client.ts
import { ProfilesClient } from '@/api/generated/profiles/client';
const cache = new Map<string, { data: any; timestamp: number }>();
class CachedProfilesClient extends ProfilesClient {
async getProfile(ttl: number = 60000): Promise<Profile> {
const cacheKey = 'profile';
const cached = cache.get(cacheKey);
if (cached && Date.now() - cached.timestamp < ttl) {
return cached.data;
}
const data = await super.getProfile();
cache.set(cacheKey, { data, timestamp: Date.now() });
return data;
}
}Query Parameters
Usage with filters
import { ProfilesClient } from '@/api/generated/profiles/client';
const client = new ProfilesClient();
// List with filters
const profiles = await client.listProfiles({
page: 1,
page_size: 10,
search: 'john',
ordering: '-created_at',
});
// Custom query params
const filtered = await client.listProfiles({
status: 'active',
created_after: '2024-01-01',
});Build Integration
Interactive Build Confirmation
By default, the command will prompt you to build:
python manage.py generate_clients --typescript
# Automatically:
# 1. Generates TypeScript clients
# 2. Copies to Next.js project (always)
# 3. Prompts: "🏗️ Build Next.js static export? (Y/n)"
# 4. If yes → Builds Next.js: cd ../django_admin && pnpm build
# 5. Creates ZIP: static/nextjs_admin.zipSkip Build Prompt
# Generate without building (no prompt)
python manage.py generate_clients --typescript --no-build
# Build manually later
cd ../django_admin/apps/admin
pnpm buildCustomization
Custom Output Path
api/config.py
config = DjangoConfig(
nextjs_admin=NextJsAdminConfig(
project_path="../django_admin",
api_output_path="src/lib/api", # Custom path
),
)TypeScript clients will always be copied automatically to the configured path.
Regeneration
When to Regenerate
Regenerate clients when:
- Django models change
- API endpoints added/removed
- Request/response schemas change
- API permissions change
# After model changes
python manage.py makemigrations
python manage.py migrate
# Regenerate clients
python manage.py generate_clients --typescriptCI/CD Integration
.github/workflows/generate-api.yml
name: Generate API Clients
on:
push:
paths:
- 'api/**'
- 'apps/*/models.py'
jobs:
generate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up Python
uses: actions/setup-python@v2
with:
python-version: '3.11'
- name: Install dependencies
run: pip install -r requirements.txt
- name: Generate API clients
run: python manage.py generate_clients --typescript
- name: Commit changes
run: |
git config --local user.email "[email protected]"
git config --local user.name "GitHub Action"
git add openapi/ django_admin/
git commit -m "chore: regenerate API clients" || exit 0
git pushTroubleshooting
Clients Not Generated
Check: OpenAPI schema generation
# Generate schema only
python manage.py spectacular --file openapi/schema.json
# Check schema
cat openapi/schema.json | jq '.paths'Type Errors
Solution: Regenerate clients
rm -rf openapi/clients/typescript/
python manage.py generate_clients --typescriptImport Errors
Check: Path alias in tsconfig.json
tsconfig.json
{
"compilerOptions": {
"paths": {
"@/*": ["./src/*"]
}
}
}Next Steps
- Configuration Reference - All config options
- Examples - Real-world usage examples
- How It Works - Architecture deep dive
- Troubleshooting - Common issues
Best Practice
Regenerate clients after every API change to ensure type safety across your stack.