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 every endpoint is tested
in-process without starting a server. This lesson 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 course 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.
1. Install
uv add "fastapi[standard]" # or: python -m pip install "fastapi[standard]"
| Package | What you get |
|---|---|
fastapi | the 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 uvicorn | when 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 lessons show.
2. Run it: the CLI or uvicorn
from fastapi import FastAPI
app = FastAPI(title="Hello API", version="0.1.0")
@app.get("/")
def root():
return {"message": "hello"}
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:
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)))
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 (lesson 20).
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. Lesson 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 course uses it:
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)
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 course filter the warning so the outputs stay readable.
5. A starting layout
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
| Tool | Why, for FastAPI specifically |
|---|---|
| ruff (lint + format) | catches unused imports, undefined names and dozens of bug patterns; formats on save |
| mypy or pyright | FastAPI is built on type hints, so a type checker verifies the same hints that drive validation |
pytest + TestClient | every endpoint tested in-process (lesson 19) |
| pre-commit and CI | run all of the above on every commit and pull request |
| your editor's debugger | run 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 devwhile coding,fastapi runoruvicorn module:appin production. - /docs, /redoc, /openapi.json come free. Decide deliberately whether production exposes them.
- TestClient exercises the app in-process: no server, instant feedback.
Checkpoint
uvicorn main:app refer to?