← Back to Blog

Flask + Next.js with flask run under the hood

·8 min read

Before v0.13.0, Flask projects ran their server in one terminal and a type watcher in another. Now one command does both, launching Flask’s own dev server, not an ASGI stand-in. Here is the full setup, which doubles as the walkthrough for examples/flask-nextjs.

1. The backend

# server/main.py
from flask import Flask
from flask_cors import CORS
from pyrpc_core import rpc
from pyrpc_flask import mount_flask

app = Flask(__name__)
CORS(app)

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

@rpc.mutation
def create_item(name: str) -> dict:
 return {"name": name, "id": 1}

mount_flask(app)

Nothing exotic: a normal Flask app, procedures decorated, two routes added. You can run it yourself with python main.py at any time, pyRPC never takes ownership away from you.

2. One command instead of three terminals

$ cd server && pyrpc dev

 pyRPC setup (runs once - saved to pyrpc.json)
 ? Backend framework: Flask <- preselected: mount_flask( sniffed in main.py
 ? Backend entry point (module[:app] - the file that calls mount_flask): main
 ? Client project root: ../client <- directory autocomplete, Tab accepts
 ? Frontend framework: Next.js <- detected from next.config.ts

 ✓ pyrpc.json created
 ✓ types generated (2 procs) → ../client/__pyrpc.ts
 pyRPC dev http://127.0.0.1:8000/rpc

What actually runs is Flask native:

python -m flask --app main:app run --host 127.0.0.1 --port 8000 --reload

No WSGI bridge, no uvicorn in the process tree. Tracebacks look like Flask because they are.

3. The frontend side

pyrpc dev wrote __pyrpc.ts into ../client and wired @pyrpc/types to it (tsconfig paths + a Turbopack alias). Your client file:

// client/lib/pyrpc.ts
import { createNextClient, httpBatchLink } from "@pyrpc/next"
import type { Types } from "@pyrpc/types"

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

Then ordinary TanStack usage, api.greet.useQuery({}), api.create_item.useMutation(), fully typed against the Python signatures.

4. Things to try

  • Edit main.py, add a procedure: types regenerate within ~300ms; autocomplete updates without touching the client.
  • Edit pyrpc.json: set "framework": "fastapi" just to watch the watcher terminate Flask and relaunch uvicorn. Set it back.
  • Start the server yourself (python main.py) on port 8000, then run pyrpc dev: it detects the running server and only runs the type watcher.

Two terminals total: backend+types in one, npm run dev in the other. That is the whole workflow.