Text size
Dark mode
RecallRunGuides & benchmarks
Learn to build real systemsGuides•Benchmarks•Experiments•Community
Search the site/Free checklist

Guides/FastAPI

FastAPI: Setup, tooling & the dev loop

A productive FastAPI setup is small: a virtual environment, fastapi with a server, auto-reload while you code, the interactive docs at /docs , and a test client so…

In this post

What this post covers
  1. 1. Install
  2. 2. Run it: the CLI or uvicorn
  3. 3. The interactive docs
  4. 4. Test in-process from day one
  5. 5. A starting layout
  6. 6. Tooling that pays for itself
  7. Recap

A productive FastAPI setup is small: a virtual environment, fastapi with a server, auto-reload while you code, the interactive docs at /docs, and a test client so every endpoint is tested in-process without starting a server. This part sets that up, runs a real uvicorn server to show what happens on the wire, and introduces the in-process testing style the rest of the guide relies on.

🧪

A kitchen with a tasting spoon

uvicorn is the restaurant's front door: real customers, real traffic. The test client is the chef's tasting spoon: you check each dish in the kitchen, instantly, without seating a customer. Good teams taste constantly and open the doors only when the dish is right.

uvicorn: the server process sockets · HTTP parsing · event loop · --reload · --workers FastAPI app (built on Starlette) routing · middleware · validation · dependencies · /docs your path operations plain def / async def functions bytes on a socket become an ASGI scope and receive/send calls validated arguments

1. Install

in a fresh virtual environment (Core Python part 02)

uv add "fastapi[standard]"           # or: python -m pip install "fastapi[standard]"
PackageWhat you get
fastapithe framework, with Starlette and Pydantic as dependencies
fastapi[standard]plus uvicorn, the fastapi CLI, httpx (test client), python-multipart (forms and uploads), email-validator, Jinja2
minimal: fastapi uvicornwhen you want to choose every dependency yourself (production images often do)

FastAPI needs Python 3.10+ (since 0.129). Pin versions for services; FastAPI is pre-1.0 and minor releases do sometimes change behaviour, as later parts show.

2. Run it: the CLI or uvicorn

main.py

from fastapi import FastAPI

app = FastAPI(title="Hello API", version="0.1.0")

@app.get("/")
def root():
    return {"message": "hello"}

two ways to serve it

fastapi dev main.py                      # development: auto-reload, binds to 127.0.0.1:8000
fastapi run main.py                      # production defaults: no reload, binds to 0.0.0.0

uvicorn main:app --reload                # the same, with uvicorn directly ("module:attribute")
uvicorn main:app --host 0.0.0.0 --port 8000 --workers 4

The CLI comes with fastapi[standard] and wraps uvicorn with good defaults. This example runs a real uvicorn server in a subprocess, talks to it over a real socket, and prints its logs:

a real server, a real HTTP request

import re
import socket
import subprocess
import sys
import time
from pathlib import Path

import httpx

Path("main.py").write_text(
    "from fastapi import FastAPI\n"
    "app = FastAPI(title='Hello API', version='0.1.0')\n\n"
    "@app.get('/')\n"
    "def root():\n"
    "    return {'message': 'hello'}\n", encoding="utf-8")

with socket.socket() as s:                         # find a free port
    s.bind(("127.0.0.1", 0))
    port = s.getsockname()[1]

server = subprocess.Popen([sys.executable, "-m", "uvicorn", "main:app", "--port", str(port)],
                          stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True)
try:
    base = f"http://127.0.0.1:{port}"
    for _ in range(100):                           # wait until it accepts connections
        try:
            r = httpx.get(base + "/")
            break
        except httpx.ConnectError:
            time.sleep(0.1)
    print(r.status_code, r.headers["content-type"], r.json())
    print("/docs serves Swagger UI:", "swagger-ui" in httpx.get(base + "/docs").text)
    print("/openapi.json info:", httpx.get(base + "/openapi.json").json()["info"])
    print("unknown path:", httpx.get(base + "/nope").status_code)
finally:
    server.terminate()
    logs = server.communicate(timeout=10)[0]
for line in logs.splitlines():
    if line.startswith("INFO") and "Uvicorn running" not in line:
        print("  log |", re.sub(r"\[\d+\]", "[pid]", re.sub(r":\d{4,5}", ":PORT", line)))

Output

200 application/json {'message': 'hello'}
/docs serves Swagger UI: True
/openapi.json info: {'title': 'Hello API', 'version': '0.1.0'}
unknown path: 404
  log | INFO:     Started server process [pid]
  log | INFO:     Waiting for application startup.
  log | INFO:     Application startup complete.
  log | INFO:     127.0.0.1:PORT - "GET / HTTP/1.1" 200 OK
  log | INFO:     127.0.0.1:PORT - "GET /docs HTTP/1.1" 200 OK
  log | INFO:     127.0.0.1:PORT - "GET /openapi.json HTTP/1.1" 200 OK
  log | INFO:     127.0.0.1:PORT - "GET /nope HTTP/1.1" 404 Not Found

3. The interactive docs

/docs

Swagger UI: every endpoint, its parameters and schemas, with a "Try it out" button that sends real requests.

/redoc

ReDoc: a clean, read-only reference, good for sharing with API consumers.

/openapi.json

The machine-readable contract. Clients, SDK generators, API gateways and contract tests all consume it (part 20).

In production

Public docs are a gift to attackers mapping your API. For internal services, keep them. For public ones, either disable them (FastAPI(docs_url=None, redoc_url=None, openapi_url=None)) or serve them behind authentication. Part 20 shows both.

4. Test in-process from day one

TestClient calls your ASGI app directly, with no server and no port, and returns ordinary response objects. It is the fastest feedback loop you have, and nearly every example in this guide uses it:

the same checks, without a server

from fastapi import FastAPI
from fastapi.testclient import TestClient

app = FastAPI(title="Hello API", version="0.1.0")

@app.get("/")
def root():
    return {"message": "hello"}

client = TestClient(app)
r = client.get("/")
assert r.status_code == 200 and r.json() == {"message": "hello"}
print(r.status_code, r.json(), "| docs:", client.get("/docs").status_code, "| missing:", client.get("/nope").status_code)

Output

200 {'message': 'hello'} | docs: 200 | missing: 404
The httpx2 warning

With Starlette 1.x, importing TestClient prints a deprecation warning suggesting httpx2, the next generation of the httpx client it is built on. It is harmless. Install httpx2 in your dev dependencies to silence it (FastAPI's own test suite did this in 0.137). The examples in this guide filter the warning so the outputs stay readable.

5. A starting layout

small enough to start, shaped to grow (part 07 expands it)

orders-api/
├── pyproject.toml
├── app/
│   ├── __init__.py
│   ├── main.py            ← create_app(): builds the FastAPI instance
│   ├── settings.py        ← pydantic-settings (lesson 08)
│   ├── api/               ← routers, one per resource
│   ├── services/          ← business logic, no HTTP in here
│   └── db/                ← models, sessions, repositories
└── tests/
    └── test_orders.py

6. Tooling that pays for itself

ToolWhy, for FastAPI specifically
ruff (lint + format)catches unused imports, undefined names and dozens of bug patterns; formats on save
mypy or pyrightFastAPI is built on type hints, so a type checker verifies the same hints that drive validation
pytest + TestClientevery endpoint tested in-process (part 19)
pre-commit and CIrun all of the above on every commit and pull request
your editor's debuggerrun uvicorn.run("main:app") from a small script, or use the editor's FastAPI launch configuration, and set breakpoints in endpoints

Recap

  • Install fastapi[standard] in a venv; Python 3.10+; pin versions.
  • Run with fastapi dev while coding, fastapi run or uvicorn module:app in production.
  • /docs, /redoc, /openapi.json come free. Decide deliberately whether production exposes them.
  • TestClient exercises the app in-process: no server, instant feedback.

Get the free RAG production checklist

25 checks to run before real users see your RAG feature: exact-match misses, invented citations, prompt injection through documents, quality that slips after a prompt change. Confirm your email and the PDF comes with the welcome email, plus each new experiment.

One email to confirm. Unsubscribe any time. What's in it · Privacy

Want the production version of this? See ShipRAG: A production RAG + MCP starter kit for FastAPI: hybrid search, validated citations, evals in CI, Docker, 32 tests.

Written by

Alby Tomy

Python/FastAPI Engineer | Data Engineer | AI Engineer building RAG and MCP systems.

X · GitHub · Contact & enquiries