Django

Mount pyRPC on a Django application using pyrpc_django.mount_django.

The Django adapter wires pyRPC into your URLconf with mount_django(urlpatterns). It uses Django's native async def views (Django 4.2+), so there is no bridge or wrapper needed.

Requirements

  • Python 3.11+
  • Django 4.2+
  • pyrpc-core[django]

1. Install

uv add pyrpc-core[django]
# or
pip install "pyrpc-core[django]"

For CORS during local development:

uv add django-cors-headers
# or
pip install django-cors-headers

2. Project scaffold

If you are starting a new project, follow the official Django tutorial to create the project structure:

django-admin startproject myproject
cd myproject
python manage.py startapp myapp

The minimum file structure pyRPC needs:

myproject/
  manage.py
  myproject/
    __init__.py
    settings.py
    urls.py
    wsgi.py

3. Settings

Add corsheaders and your app to INSTALLED_APPS. Put CorsMiddleware first in MIDDLEWARE -- this is required by django-cors-headers.

myproject/settings.py
INSTALLED_APPS = [
    "corsheaders",
    "django.contrib.admin",
    "django.contrib.auth",
    "django.contrib.contenttypes",
    "django.contrib.sessions",
    "django.contrib.messages",
    "django.contrib.staticfiles",
    "myproject",
]

MIDDLEWARE = [
    "corsheaders.middleware.CorsMiddleware",   # must be first
    "django.middleware.security.SecurityMiddleware",
    "django.contrib.sessions.middleware.SessionMiddleware",
    "django.middleware.common.CommonMiddleware",
    "django.middleware.csrf.CsrfViewMiddleware",
    "django.contrib.auth.middleware.AuthenticationMiddleware",
    "django.contrib.messages.middleware.MessageMiddleware",
    "django.middleware.clickjacking.XFrameOptionsMiddleware",
]

CORS_ALLOWED_ORIGINS = [
    "http://localhost:3000",   # React / Next.js
    "http://localhost:5173",   # Vue / Svelte (Vite)
]
CORS_ALLOW_CREDENTIALS = True

4. Write procedures

Procedures are functions decorated with @rpc.query or @rpc.mutation. Django 4.2+ runs async def procedures as native async views with no bridge needed; plain def procedures work too.

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}


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

5. Wire the URLs

Call mount_django(urlpatterns) in your URLconf. The import of views at the top is required -- it executes the @rpc decorators. Without it the procedures are never registered and /rpc returns an empty schema.

myproject/urls.py
from django.contrib import admin
from django.urls import path
from pyrpc_django import mount_django
from . import views   # required: triggers @rpc decorator execution

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

mount_django appends two URL patterns to the list:

  • POST /rpc -- procedure dispatch (native async view)
  • GET /rpc -- schema introspection (used by pyrpc dev and the Python client)

6. Start the dev server

Run pyrpc dev from the directory containing manage.py to generate and keep TypeScript types in sync:

pyrpc dev --yes --framework django --module myproject.views --client ../client

First run: the wizard asks for your backend framework (Django), the path to manage.py, and a types module - the module whose import registers your @rpc procedures (usually the views.py that declares them, e.g. myproject.views; Django's settings and manage.py register nothing). This is written to pyrpc.json. Every run after reads it automatically.

pyrpc dev launches Django's own development server (manage.py runserver) for you, generates __pyrpc.ts in the client project, and regenerates it on every .py save. If you prefer to manage the server yourself, run it separately:

python manage.py runserver

Django runs the async RPC views natively -- no separate WSGI/ASGI bridge required.

7. Connect the frontend

lib/pyrpc.ts
import { createReactClient, httpBatchLink } from "@pyrpc/react"
import type { Types } from "@pyrpc/types"

export const api = createReactClient<Types>({
  links: [
    httpBatchLink({
      url: "http://localhost:8000",
    }),
  ],
})

See React, Next.js, Vue, or Svelte for full frontend setup per framework.

How it works

  • @rpc.query / @rpc.mutation register the function in the global procedure registry and tag it with a kind.
  • mount_django(urlpatterns) appends the two RPC routes to your URLconf. The views are csrf_exempt and return JSON directly.
  • The from . import views line in urls.py is what causes Python to execute the module and run the @rpc decorators. This is the same mechanism Django uses for signals and AppConfig.ready().
  • async def procedures use Django 4.2+'s native async view dispatch. There is no anyio.run wrapper.

Custom router

If you want to isolate procedures by app rather than using the global default router:

myapp/procedures.py
from pyrpc_core import Router

router = Router()

@router.query
async def list_items() -> list:
    return []

@router.mutation
async def create_item(name: str) -> dict:
    return {"name": name, "created": True}
myproject/urls.py
from myapp.procedures import router
from pyrpc_django import mount_django

urlpatterns = []
mount_django(urlpatterns, router=router)

Full working examples

ExampleFrontendSource
Django + ReactReact + TanStack Queryexamples/django-react
Django + Next.jsNext.js App Router + RSCexamples/django-nextjs
Django + VueVue 3 + TanStack Vue Queryexamples/django-vue
Django + SvelteSvelte + TanStack Svelte Queryexamples/django-svelte