On this page

Tutorial9 min read

How to Debug a FastAPI Application (Complete VS Code Guide)

Step-by-step guide to debugging FastAPI in VS Code: validation errors, breakpoints, async debugging, and AI-powered root cause analysis.

FastAPIPythondebuggingVS Codetutorial

Why FastAPI Debugging Is Different

FastAPI runs on ASGI (async I/O). That means two things for debugging: errors happen in async context, and standard print-debugging often misses the timing of when things fail. A validation error before your function runs looks identical in the terminal to a runtime error inside your function, until you know where to look.

This guide covers the full debugging workflow: reading FastAPI's built-in error output, setting up VS Code breakpoints for async code, and using AI tools for cross-file root cause analysis.

Step 1: Read the Terminal Output First

When FastAPI starts with uvicorn app.main:app --reload, every request and every error prints to the terminal. Do not jump to the code before reading it.

A 422 validation error looks like:

INFO:     127.0.0.1:54234 - "POST /users HTTP/1.1" 422 Unprocessable Entity

A 500 server error looks like:

INFO:     127.0.0.1:54234 - "POST /users HTTP/1.1" 500 Internal Server Error
ERROR:    Exception in ASGI application
Traceback (most recent call last):
  File "/app/routers/users.py", line 34, in create_user
    result = await db.insert(user.dict())
AttributeError: 'NoneType' object has no attribute 'insert'

The status code tells you where the error originated:

StatusOriginWhere to look
422Pydantic validationRequest body vs model definition
401/403Auth middlewareDependency functions, token validation
500Your route functionTraceback in terminal
502/504Upstream serviceExternal API calls, DB connection

Step 2: Use /docs for Request Validation

FastAPI auto-generates interactive docs at http://localhost:8000/docs. Before writing any debug code, test your endpoint directly in Swagger UI.

Tip: Swagger UI is generated from your actual Pydantic models. It shows exactly what FastAPI expects. If your request body matches the Swagger schema and still fails, the bug is inside your route function, not in the request.

For 422 errors specifically, the /docs schema shows required vs optional fields, expected types, and field constraints. Compare your actual request against it directly.

Step 3: Add Structured Logging

print() works for simple cases. For async FastAPI, structured logging gives you context you cannot get from print:

python
import logging
import structlog

logger = structlog.get_logger()

@app.post("/orders")
async def create_order(order: CreateOrder, user_id: str = Depends(get_current_user)):
    logger.info("create_order.start", user_id=user_id, item_id=order.item_id)

    try:
        result = await order_service.create(order, user_id)
        logger.info("create_order.success", order_id=result.id)
        return result
    except Exception as e:
        logger.error("create_order.failed", error=str(e), user_id=user_id)
        raise

structlog adds key-value pairs to every log line. When you search the logs for a specific user or order, you see the full sequence of events, not just where it crashed.

Step 4: Set Up VS Code Debugger for FastAPI

VS Code's Python debugger works with FastAPI but needs a launch configuration that handles uvicorn correctly.

Create .vscode/launch.json in your project root:

json
{
  "version": "0.2.0",
  "configurations": [
    {
      "name": "FastAPI Debug",
      "type": "debugpy",
      "request": "launch",
      "module": "uvicorn",
      "args": [
        "app.main:app",
        "--reload",
        "--port",
        "8000"
      ],
      "jinja": true,
      "justMyCode": true
    }
  ]
}

Note: Use "module": "uvicorn" not "program": "uvicorn". The module form handles Python path resolution correctly across virtual environments and makes breakpoints in your code work reliably.

Set a breakpoint by clicking the gutter to the left of a line number inside any route function. Press F5 to start debugging. Send a request and execution pauses at your breakpoint so you can inspect all variables.

Step 5: Debug Async Functions

Await points are common places for bugs. The awaited coroutine can raise, return None, or hang. Set breakpoints on the await line to inspect what happens:

python
@app.get("/users/{user_id}")
async def get_user(user_id: str):
    user = await db.find_user(user_id)  # set breakpoint here
    if not user:
        raise HTTPException(status_code=404, detail="User not found")
    return user

When the debugger pauses at that line, F10 (step over) executes the await and moves to the next line. The user variable in the debug panel shows what db.find_user() actually returned, including None if the record does not exist.

Warning: Do not set breakpoints inside async generator functions or inside asyncio.gather() calls. The debugger can lose track of coroutine context in those cases. Wrap the suspicious code in a regular async function and debug that instead.

Step 6: Debug Dependencies

FastAPI's dependency injection runs before your route function. Bugs in dependencies show stack traces that point into FastAPI internals, not your code, which is confusing.

python
async def get_current_user(token: str = Depends(oauth2_scheme)):
    credentials_exception = HTTPException(status_code=401)
    try:
        payload = jwt.decode(token, SECRET_KEY, algorithms=["HS256"])
        user_id = payload.get("sub")  # set breakpoint here
    except JWTError:
        raise credentials_exception
    return await db.get_user(user_id)

Set a breakpoint inside the dependency function itself. When a request hits an endpoint that uses this dependency, the debugger pauses in the dependency and lets you inspect the token, payload, and user_id before the route function runs.

Step 7: Reproduce Production Errors Locally

Production FastAPI errors often include a traceback in your logs. To reproduce:

  1. Copy the exact request body from your logs
  2. Paste it into /docs, click "Try it out", and send
  3. If it fails locally, you have reproduced it

For errors that only happen with specific database state, add a temporary debug endpoint:

python
# Remove after debugging
@app.get("/debug/user/{user_id}", include_in_schema=False)
async def debug_user(user_id: str):
    user = await db.find_user(user_id)
    order_count = await order_service.count_for_user(user_id)
    return {"user": user, "order_count": order_count, "has_team": user.team_id is not None}

include_in_schema=False hides it from Swagger. Gives you a debugging endpoint without polluting your API surface.

Step 8: Use AI for Cross-File Errors

FastAPI apps span multiple files: routes, services, models, dependencies, database layer. When a traceback points deep into your service layer, the root cause is often 3 to 4 files away from where the error surfaces.

Error: AttributeError: 'NoneType' object has no attribute 'stripe_customer_id' in billing_service.py line 47

Manual approach: read the traceback, find what called billing_service, find what passed the user object, find where user was loaded, find where it might return None.

DebugAI approach: highlight the error and click Debug with AI. It reads the traceback, loads billing_service.py, loads the caller, loads the user model and query, and tells you that get_user() in users.py returns None for users who registered before the stripe_customer_id column was added and the schema migration was not backfilled.

Cross-file bugs in async services are the fastest DebugAI use case. The dependency chain is too long to trace manually in a reasonable time.

Common FastAPI Errors Quick Reference

ErrorLikely causeFirst thing to check
422 Unprocessable EntityPydantic validation faildetail array in response body
500 Internal Server ErrorException in route or dependencyTerminal traceback
401 UnauthorizedAuth dependency failedJWT decode, token expiry
404 Not FoundWrong route or HTTPException(404)Route path, HTTP method
RuntimeError: no running event loopSync code calling asyncEnsure await on async functions
greenlet_spawn has not been calledSQLAlchemy async session misuseUse AsyncSession, not Session

FastAPI with the VS Code debugger covers 90% of bugs. For the rest, cross-service async timing issues and production-only database state bugs, paste the error and relevant files into DebugAI and it will trace the full dependency chain without hours of log-reading.

Debug faster starting today.

Free VS Code extension. 10 sessions/day. No credit card.

Install Free →

Related Posts

Tutorial

How to Debug a Next.js Application in VS Code (Complete Guide)

8 min read

Tutorial

Fix KeyError in Python: 5 Causes and How to Find the Source

5 min read

← All posts