← Back to Blog

FastAPI + React: full-stack type safety from zero

·10 min read

The fastapi-react example is the most direct way to understand what pyRPC does. You write Python functions, decorate them with @rpc.query or @rpc.mutation, and the React side gets fully typed useQuery / useMutation hooks, no schema file, no codegen step you have to run manually.

Prerequisites

You need Python 3.11+, Node 18+, and either uv or pip.

Project layout

fastapi-react/
  server/
    main.py           ← FastAPI app + pyRPC procedures
    pyrpc.json        ← written by pyrpc dev on first run
  client/
    src/
      pyrpc.ts        ← createReactClient setup
      index.tsx       ← api.Provider wraps the app
      App.tsx         ← useQuery / useMutation calls

Step 1, the server

Three procedures: two queries (read operations) and one mutation (write operation). The decorator kind is the only thing that differs, pyRPC uses it to generate the right hook type on the frontend.

# server/main.py
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from pyrpc_core import rpc
from pyrpc_fastapi import mount_fastapi

app = FastAPI()

app.add_middleware(
    CORSMiddleware,
    allow_origins=["http://localhost:3000"],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

@rpc.query
def read_root():
    return {"Hello": "World"}

@rpc.query
def read_item(item_id: int, q: str = None):
    return {"item_id": item_id, "q": q}

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

mount_fastapi(app)

Step 2, start pyrpc dev

cd server
uv add pyrpc-core[fastapi]
pyrpc dev

First run: a 2-question wizard writes pyrpc.json. Every run after: reads that file automatically. If you want to skip the wizard entirely:

# auto-detect module and output
pyrpc dev --yes

# fully explicit: CI-safe
pyrpc dev --yes --module main --client ../client

pyRPC starts uvicorn on :8000, writes __pyrpc.ts in the client, and re-generates it on every .py save.

Step 3, client setup

# 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",
})

Wrap the root component with api.Provider, this is the TanStack Query cache boundary:

// src/index.tsx
import { api } from "./pyrpc"

root.render(
  <api.Provider>
    <App />
  </api.Provider>
)

Step 4, call the procedures

// src/App.tsx
import { useState } from "react"
import { api } from "./pyrpc"

function App() {
  const [name, setName] = useState("")

  const { data: greeting, isLoading } = api.read_root.useQuery()
  const { data: item } = api.read_item.useQuery({ item_id: 42, q: "test" })
  const createItem = api.create_item.useMutation()

  return (
    <div>
      {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, description: `Item: ${name}` })}
        disabled={createItem.isPending}
      >
        {createItem.isPending ? "Creating…" : "Create"}
      </button>
      {createItem.isSuccess && <pre>{JSON.stringify(createItem.data)}</pre>}
    </div>
  )
}

Run it

# Terminal 1
cd server && pyrpc dev

# Terminal 2
cd client && npm install && npm start

Open http://localhost:3000. The app queries all three procedures and renders the results. Rename a procedure in Python, TypeScript flags the broken call immediately.

Next steps

The same FastAPI server works with Next.js, Vue, and Svelte, only the frontend adapter changes. See the examples directory for all 12 combinations.