Debugging FastAPI in VSCode
Print-debugging through a request flow misses things. By the time print(payload) shows up in your terminal, the request has already been parsed, validated, queried against the database, and partially serialized. A real debugger lets you pause at any of those points and look around.
VSCode ships a Python debugger that works well with FastAPI.
Configuring launch.json
VSCode launches the debugger using a config file at .vscode/launch.json. Create the file if it doesn't exist, and add this configuration:
{
"version": "0.2.0",
"configurations": [
{
"name": "FastAPI: debug",
"type": "debugpy",
"request": "launch",
"module": "uvicorn",
"args": ["release_tracker.main:app", "--port", "8001"],
"cwd": "${workspaceFolder}/src",
"console": "integratedTerminal"
}
]
}
- It launches
uvicorndirectly instead offastapi dev. The dev wrapper uses subprocess-based hot reload, and breakpoints don't fire reliably inside subprocesses. Plainuvicornruns in the same process as the debugger. - The debug server listens on port
8001so it doesn't conflict withuv run fastapi dev(which uses8000).
You can keep the regular dev server running on 8000 while a debug session runs alongside on 8001.
cwdis set to${workspaceFolder}/srcso therelease_trackerpackage resolves correctly.integratedTerminalkeeps logs visible in VSCode's terminal panel instead of the debug console, which is easier to read.
To start the debugger, open the Run and Debug view (the bug-and-play icon in the activity bar), pick FastAPI: debug from the dropdown, and press the green play button (or F5).
What you give up: hot reload
This config trades hot reload for working breakpoints. When you change a route handler, you'll need to stop and restart the debugger. For everyday iteration, keep using uv run fastapi dev. For stepping through a specific bug, use this config.
Setting a Breakpoint
Open src/release_tracker/main.py and click in the gutter just to the left of the line number on this line in get_project:
project = session.get(Project, project_id)
A red dot appears. That's a breakpoint. With the debugger running, request GET /projects/1 from the docs page (http://127.0.0.1:8001/docs). Execution pauses at the breakpoint before session.get fires.
Inspecting Variables
VSCode's left panel shows debugger views while paused:
-
Variables:
The current frame's locals.
project_idis already populated from the path parameter;sessionis the injected SQLModelSession. -
Watch:
Expressions you want to evaluate continuously. Add
session.bind.urlto see which database the session is connected to. -
Call Stack:
The chain of frames that got you here. The top is
get_project. Below it are FastAPI's request-dispatch frames. You can click any frame to jump to its locals.
Hover over any variable in the editor to see its current value. For complex objects (like session), expand it in the Variables panel to drill into attributes.
Stepping Through
Debug controls live at the top of the editor while paused:
-
Continue (F5):
Run until the next breakpoint or the request finishes.
-
Step Over (F10):
Execute the current line and pause on the next one in the same function.
-
Step Into (F11):
Descend into the function being called on the current line.
-
Step Out (Shift+F11):
Finish the current function and pause in the caller.
-
Restart / Stop:
Restart or kill the debug session.
For the route handler above, F10 advances past session.get(...) and lets you inspect the resulting project before the if not project check fires.
Where This Stops
This is enough to get unblocked on most route-handler bugs. VSCode's debugger has a lot more to it: conditional breakpoints, log points (no-pause logging), watch expressions that mutate state, evaluate expressions in the debug console, attach to a running process. When you're ready, the VSCode Python debugging docs walk through the rest.