# mizan Django + React server functions framework. RPC, not REST. You define Python functions. mizan generates typed React hooks. No API routes, no serializers, no endpoint boilerplate. ```python # Django @client(context='global') def current_user(request) -> UserOutput: return UserOutput(email=request.user.email) ``` ```tsx // React (generated) const user = useCurrentUser() // typed, SSR-hydrated, auto-refreshed ``` ## Packages | Package | Path | Install | |---------|------|---------| | `mizan` (Python) | `django/` | `uv add "mizan[channels] @ git+..."` | | `@rythazhur/mizan` (TypeScript) | `react/` | `npm install @rythazhur/mizan@git+...` | ## Quick Start ### 1. Django setup ```python # settings.py INSTALLED_APPS = [ "mizan", "myapp", ] # urls.py from django.urls import include, path urlpatterns = [ path("api/mizan/", include("mizan.urls")), ] # asgi.py (for WebSocket support) from mizan import wrap_asgi from django.core.asgi import get_asgi_application application = wrap_asgi(get_asgi_application()) ``` ### 2. Define server functions ```python # myapp/mizan_clients.py from django.http import HttpRequest from mizan.client import client from mizan.setup.registry import register from pydantic import BaseModel class EchoOutput(BaseModel): message: str @client def echo(request: HttpRequest, text: str) -> EchoOutput: return EchoOutput(message=text) register(echo, "echo") ``` ### 3. Register in apps.py ```python class MyAppConfig(AppConfig): name = "myapp" def ready(self): import myapp.mizan_clients # noqa: F401 ``` ### 4. Generate TypeScript ```bash # django.config.mjs export default { source: { django: { managePath: '../backend/manage.py', command: ['uv', 'run', 'python'], }, }, output: 'src/api/generated.ts', } ``` ```bash npx mizan-generate ``` This produces typed hooks, a typed provider, form hooks with Zod validation, and channel hooks. ### 5. Use in React ```tsx // layout.tsx import { DjangoContext } from '@/api' export default function Layout({ children }) { return {children} } ``` ```tsx // page.tsx import { useEcho, useCurrentUser, DjangoError } from '@/api' function MyComponent() { const user = useCurrentUser() const echo = useEcho() const handleClick = async () => { try { const result = await echo({ text: 'hello' }) console.log(result.message) // typed } catch (e) { if (e instanceof DjangoError) { console.log(e.code) // NOT_FOUND, VALIDATION_ERROR, etc. } } } } ``` ## Features | Backend | Frontend (generated) | Transport | |---------|---------------------|-----------| | `@client` | `useXxx()` | HTTP | | `@client(context='global')` | `useXxx()` + SSR hydration | HTTP | | `@client(context='local')` | `useXxx()` with params | HTTP | | `@client(websocket=True)` | `useXxx()` | WebSocket RPC | | `@client(auth=True\|'staff'\|callable)` | Auth errors as `DjangoError` | HTTP | | `mizanFormMixin` | `useXxxForm()` + Zod validation | HTTP | | `ReactChannel` | `useXxxChannel()` | WebSocket | | `@compose(...)` | Combined providers | varies | ## Architecture ``` React app └─ ← generated provider (includes ChannelProvider) ├─ useCurrentUser() ← generated context hook (SSR-hydrated) ├─ useEcho() ← generated function hook ├─ useContactForm() ← generated form hook (Zod + server validation) └─ useChatChannel() ← generated channel hook (WebSocket) │ ├─ HTTP: POST /api/mizan/call/ { fn: "echo", args: { text: "hi" } } └─ WS: { action: "rpc", fn: "echo", args: { text: "hi" } } │ Django executor ├─ Pydantic input validation ├─ Auth check (session, JWT, or custom) ├─ Function execution └─ Pydantic output serialization ``` The generated `DjangoContext` is the **only provider** needed. It wraps `mizanProvider` + `ChannelProvider` and handles session init, CSRF, context auto-fetching, and WebSocket connection. ## Code Generation `npx mizan-generate` reads Django schemas (no running server needed) and produces: | File | Contents | |------|----------| | `generated.mizan.ts` | Pydantic model types (via openapi-typescript) | | `generated.django.tsx` | `DjangoContext` provider + all typed hooks | | `generated.django.server.ts` | SSR hydration helper (`getDjangoHydration`) | | `generated.forms.ts` | Form hooks with Zod schemas (`useContactForm`, etc.) | | `generated.channels.ts` | Channel message types | | `generated.channels.hooks.tsx` | Channel hooks (`useChatChannel`, etc.) | | `index.ts` | Consolidated re-exports | ## Error Handling All errors from server functions are thrown as `DjangoError`: ```tsx try { await echo({ text: 'hello' }) } catch (e) { if (e instanceof DjangoError) { e.code // 'NOT_FOUND' | 'VALIDATION_ERROR' | 'UNAUTHORIZED' | 'FORBIDDEN' | ... e.message // Human-readable message e.details // Field-level validation errors, etc. e.isAuthError() e.isValidationError() e.getFieldErrors('email') } } ``` Error codes: `NOT_FOUND`, `VALIDATION_ERROR`, `UNAUTHORIZED`, `FORBIDDEN`, `BAD_REQUEST`, `INTERNAL_ERROR`, `NOT_IMPLEMENTED`. ## Forms Django forms get typed React hooks with client-side Zod validation: ```python # Django class ContactForm(mizanFormMixin, forms.Form): mizan = mizanFormMeta( name="contact", title="Contact Us", submit_label="Send", live_validation=True, ) name = forms.CharField(max_length=100) email = forms.EmailField() message = forms.CharField(widget=forms.Textarea) def on_submit_success(self, request): send_email(self.cleaned_data) return {"sent": True} ``` ```tsx // React (generated) const form = useContactForm() form.schema // { fields: { name: {...}, email: {...} }, title, submit_label } form.data // { name: '', email: '', message: '' } form.set('email', v) // typed setter form.errors // field-level errors (Zod + server) form.submit() // → { success: true, data: { sent: true } } ``` ## Channels WebSocket channels with typed messages: ```python # Django class ChatChannel(ReactChannel): class Params(BaseModel): room: str class ReactMessage(BaseModel): text: str class DjangoMessage(BaseModel): text: str user: str def authorize(self, params): return self.user.is_authenticated def group(self, params): return f"chat_{params.room}" def receive(self, params, msg): return self.DjangoMessage(text=msg.text, user=self.user.email) ``` ```tsx // React (generated) const chat = useChatChannel({ room: 'general' }) chat.status // 'connecting' | 'connected' | 'disconnected' chat.messages // ChatDjangoMessage[] chat.send({ text: 'hello' }) ``` ## Testing ```bash # Django unit tests cd django && uv sync --extra dev --extra channels && uv run pytest # React unit tests cd react && npm test # E2E integration tests (real browser, real backend) docker compose -f examples/django-react-site/docker-compose.test.yml up -d cd examples/django-react-site/harness && npm install && npx mizan-generate && npx vite --port 5174 & npx playwright test # All at once make test-all ``` ## Project Structure ``` mizan/ django/ Python package (mizan) react/ TypeScript package (@rythazhur/mizan) examples/ django-react-site/ E2E tests, React harness, Django backend django-react-desktop-app/ PyWebView desktop app Makefile Test orchestration ```