FastAPI Compiler
Define routes and call them. A real FastAPI app running in your browser - no server, no install, nothing sent anywhere.
← VizLearnWhat this is
FastAPI 0.115 on Pydantic 2.7 and CPython 3.12, all compiled to WebAssembly and running on your own machine. Nothing is uploaded and no server is involved.
The first Run takes a few seconds while the
interpreter and the library download. After that it is immediate.
Where is the server?
There isn't one, and there cannot be: a browser tab cannot listen on a port. But a server is not what makes FastAPI work.
Underneath, a FastAPI app is one async function that takes a request as a dictionary and sends a response back as messages. That interface is called ASGI, and uvicorn's whole job is to translate real network traffic into it. Skip the network and call the app directly and everything above that line — routing, type coercion, validation, status codes, the OpenAPI schema — behaves exactly as it does in production, because it is the same code.
That is what client does. It is defined for you before
your code runs, and it has the shape of
fastapi.testclient.TestClient, so what you write here is
what you would write in a test file.
What works
- Path and query parameters, with the coercion and the 422 that follow from the annotations.
- Pydantic request bodies and
response_model. HTTPException,status_code, custom headers,Depends.APIRouter, middleware, exception handlers.app.openapi()— the schema that becomes the interactive docs.- Both
defandasync defendpoints.
What does not
uvicorn.run(...)and anything else that needs a socket. Useclientinstead.- Real I/O inside an endpoint — a network call, an
asyncio.sleep, a database driver. There is no event loop to suspend into, and you will get a clear error rather than a hang. - The
/docspage, which needs a browser pointed at a running server.app.openapi()gives you the schema behind it. - WebSockets, background tasks that outlive the request, and
file uploads —
python-multipartis not loaded.
One honest difference
In a real server a def endpoint is handed to a thread
pool so it cannot block the event loop, while an
async def one runs on the loop itself. That distinction
is most of what "should this be async?" is about.
This runtime cannot start threads, so def endpoints
are run inline here. Everything you can observe — the response,
the status, the validation — is identical, and the difference
only shows up in a blocking call, which cannot happen here anyway. It
is worth knowing about before you reason about performance from what
you see on this page.
Reading a 422
422 is FastAPI telling you the request never reached your function.
The body it returns is a Pydantic error list, and the useful field is
loc — the path to the thing it rejected.
["path", "book_id"] means the URL, and
["query", "verbose"] means the query string. Anything
starting ["body", ...] is inside the JSON you sent, with
the rest of the list naming the field — so
["body", "price"] is one level in. A 422 you did not
expect is nearly always an annotation promising something the caller
did not send.
Things worth trying
- Add
response_model=Bookto a route, then return a dict with an extra key and see it filtered out. - Write a dependency with
Dependsand use it in two routes. - Print
app.openapi()in full and find the constraint you wrote withField(gt=0). - Change a parameter's annotation from
inttostrand watch which requests start passing. - Add a second router with
APIRouter(prefix="/v2")and include it.
The layer below
Most of what FastAPI does with your annotations is Pydantic doing it. The Pydantic compiler is the same editor with just that library, and the Python compiler is the language on its own.