← Back to Blog

Django + React: native async views, typed React hooks

·11 min read

Django's async view support (4.2+) makes it a natural fit for pyRPC. Procedures are just async def functions decorated with @rpc.query / @rpc.mutation, and Django handles them natively, no anyio.run bridge.

There's one Django-specific thing to know: procedures are registered by executing their decorator, which happens when the module is imported. You must import views in urls.py to trigger registration.

Server, views.py

# myproject/views.py
from django.http import HttpResponse
from pyrpc_core import rpc

def index(request):
    return HttpResponse("<h1>Django + pyRPC</h1>")

@rpc.query
async def greet(name: str = "World") -> dict:
    return {"message": f"Hello, {name}!", "framework": "Django"}

@rpc.query
async def read_item(item_id: int, q: str = None) -> dict:
    return {"item_id": item_id, "q": q, "framework": "Django"}

@rpc.mutation
async def create_item(name: str, description: str = None) -> dict:
    return {"name": name, "description": description, "created": True}

Server, urls.py

# myproject/urls.py
from django.contrib import admin
from django.urls import path
from pyrpc_django import mount_django
from . import views  # ← this import registers the @rpc decorators

urlpatterns = [
    path("admin/", admin.site.urls),
    path("", views.index, name="index"),
]
mount_django(urlpatterns)

The import is required. Without from . import views, the procedures never run their decorators and /rpc returns an empty schema.

CORS, settings.py

INSTALLED_APPS = ["corsheaders", ...]
MIDDLEWARE = ["corsheaders.middleware.CorsMiddleware", ...]
CORS_ALLOWED_ORIGINS = ["http://localhost:3000"]

Start the dev server

cd server
uv add pyrpc-core[django]
pyrpc dev --yes --module myproject.views --client ../client

Client, identical to fastapi-react

// src/pyrpc.ts
import { createReactClient } from "@pyrpc/react"
import type { Types } from "@pyrpc/types"

export const api = createReactClient<Types>({
  baseUrl: process.env.REACT_APP_API_URL ?? "http://localhost:8000",
})
// src/App.tsx
import { useState } from "react"
import { api } from "./pyrpc"

function App() {
  const [name, setName] = useState("")
  const { data: greeting, isLoading } = api.greet.useQuery({ name: "Django User" })
  const { data: item } = api.read_item.useQuery({ item_id: 42, q: "test" })
  const createItem = api.create_item.useMutation()

  return (
    <api.Provider>
      {isLoading ? <p>Loading…</p> : <pre>{JSON.stringify(greeting)}</pre>}
      <pre>{JSON.stringify(item)}</pre>
      <input value={name} onChange={e => setName(e.target.value)} />
      <button onClick={() => createItem.mutate({ name })} disabled={createItem.isPending}>
        {createItem.isPending ? "Creating…" : "Create"}
      </button>
      {createItem.isSuccess && <pre>{JSON.stringify(createItem.data)}</pre>}
    </api.Provider>
  )
}

Run it

cd server && pyrpc dev
cd client && npm install && npm start

Open http://localhost:3000. Full source at examples/django-react.