Skip to content

FastAPI 404 Not Found Error - Complete Debugging Guide

FastAPI 404 errors are among the most common issues developers encounter when building APIs. This guide provides systematic solutions to identify and fix 404 errors in your FastAPI applications.

Common 404 Error Scenarios

1. Route Path Mismatches

Problem: URL path doesn't match route definition

# Route definition
@app.get("/users/{user_id}")
async def get_user(user_id: int):
    return {"user_id": user_id}

# These will cause 404 errors:
# GET /user/123 (missing 's')
# GET /users/ (missing user_id)
# GET /Users/123 (wrong case)

Solution:

# Correct route definitions
@app.get("/users/{user_id}")
async def get_user(user_id: int):
    return {"user_id": user_id}

@app.get("/users/")  # Handle list endpoint separately
async def list_users():
    return {"users": []}

2. HTTP Method Mismatches

Problem: Using wrong HTTP method for the route

# Route only accepts POST
@app.post("/users/")
async def create_user(user_data: dict):
    return user_data

# This will cause 404:
# GET /users/ (method not allowed, returns 404 in some cases)

Solution:

# Define routes for different methods
@app.get("/users/")
async def list_users():
    return {"users": []}

@app.post("/users/")
async def create_user(user_data: dict):
    return user_data

3. Missing Route Includes

Problem: Router not included in main app

# user_router.py
from fastapi import APIRouter

router = APIRouter()

@router.get("/users/")
async def get_users():
    return {"users": []}

# main.py - WRONG (router not included)
from fastapi import FastAPI
app = FastAPI()
# Missing: app.include_router(router)

Solution:

# main.py - CORRECT
from fastapi import FastAPI
from user_router import router as user_router

app = FastAPI()
app.include_router(user_router, prefix="/api", tags=["users"])

4. Path Parameter Type Issues

Problem: Path parameter type validation fails

@app.get("/users/{user_id}")
async def get_user(user_id: int):
    return {"user_id": user_id}

# These cause 404 (not validation error):
# GET /users/abc (string instead of int)
# GET /users/123.45 (float instead of int)

Solution:

from typing import Union
from fastapi import HTTPException

@app.get("/users/{user_id}")
async def get_user(user_id: str):  # Accept string first
    try:
        user_id_int = int(user_id)
    except ValueError:
        raise HTTPException(status_code=400, detail="Invalid user ID format")

    return {"user_id": user_id_int}

# Or use Union types for flexibility
@app.get("/items/{item_id}")
async def get_item(item_id: Union[int, str]):
    return {"item_id": item_id}

Debugging Methods

1. Enable Route Listing

from fastapi import FastAPI

app = FastAPI()

# Add this after all routes are defined
@app.on_event("startup")
async def startup_event():
    routes = []
    for route in app.routes:
        if hasattr(route, 'methods'):
            routes.append(f"{list(route.methods)} {route.path}")
    print("Available routes:")
    for route in routes:
        print(f"  {route}")

2. Add Debugging Middleware

from fastapi import FastAPI, Request
import logging

app = FastAPI()

@app.middleware("http")
async def log_requests(request: Request, call_next):
    logger = logging.getLogger("fastapi")
    logger.info(f"Request: {request.method} {request.url}")

    response = await call_next(request)

    if response.status_code == 404:
        logger.warning(f"404 Not Found: {request.method} {request.url}")

    return response

3. Use FastAPI's Automatic Documentation

Check your routes at http://localhost:8000/docs to see all available endpoints.

Advanced 404 Scenarios

1. Trailing Slash Issues

# These are different routes in FastAPI
@app.get("/users")    # No trailing slash
@app.get("/users/")   # With trailing slash

# Solution: Handle both
@app.get("/users")
@app.get("/users/")
async def get_users():
    return {"users": []}

2. Route Order Matters

# WRONG - specific route after generic route
@app.get("/users/{user_id}")
async def get_user(user_id: str):
    return {"user_id": user_id}

@app.get("/users/me")  # This will never be reached
async def get_current_user():
    return {"user": "current"}

# CORRECT - specific routes first
@app.get("/users/me")
async def get_current_user():
    return {"user": "current"}

@app.get("/users/{user_id}")
async def get_user(user_id: str):
    return {"user_id": user_id}

3. Subdirectory Deployment

# When deploying under subdirectory like /api/v1
app = FastAPI(root_path="/api/v1")

# Or use proxy headers
app = FastAPI()

@app.middleware("http")
async def add_root_path(request: Request, call_next):
    # Handle X-Forwarded-Prefix header
    forwarded_prefix = request.headers.get("X-Forwarded-Prefix")
    if forwarded_prefix:
        request.scope["root_path"] = forwarded_prefix

    return await call_next(request)

Testing for 404 Errors

1. Basic Route Testing

from fastapi.testclient import TestClient
from main import app

client = TestClient(app)

def test_existing_route():
    response = client.get("/users/123")
    assert response.status_code == 200

def test_non_existing_route():
    response = client.get("/nonexistent")
    assert response.status_code == 404

def test_wrong_method():
    response = client.get("/users/")  # If only POST is defined
    assert response.status_code in [404, 405]  # Method not allowed

2. Automated Route Testing

import pytest
from fastapi.testclient import TestClient

@pytest.fixture
def client():
    return TestClient(app)

def test_all_documented_routes(client):
    """Test that all documented routes are accessible"""
    for route in app.routes:
        if hasattr(route, 'methods') and hasattr(route, 'path'):
            for method in route.methods:
                if method in ['GET', 'POST', 'PUT', 'DELETE']:
                    # Test with dummy data for path parameters
                    path = route.path.replace('{', '123').replace('}', '')
                    response = getattr(client, method.lower())(path)
                    assert response.status_code != 404, f"Route {method} {route.path} returns 404"

Custom 404 Handler

from fastapi import FastAPI, Request, HTTPException
from fastapi.responses import JSONResponse

app = FastAPI()

@app.exception_handler(404)
async def not_found_handler(request: Request, exc: HTTPException):
    return JSONResponse(
        status_code=404,
        content={
            "error": "Not Found",
            "message": f"The requested URL {request.url.path} was not found",
            "suggestions": [
                "Check the URL spelling",
                "Verify the HTTP method",
                "Check available routes at /docs"
            ]
        }
    )

Prevention Best Practices

1. Consistent Route Naming

# Use consistent patterns
@app.get("/users/")           # List all users
@app.post("/users/")          # Create user
@app.get("/users/{user_id}")  # Get specific user
@app.put("/users/{user_id}")  # Update user
@app.delete("/users/{user_id}")  # Delete user

2. Route Organization

# Group related routes in routers
from fastapi import APIRouter

users_router = APIRouter(prefix="/users", tags=["users"])
posts_router = APIRouter(prefix="/posts", tags=["posts"])

@users_router.get("/")
async def list_users():
    return {"users": []}

@users_router.get("/{user_id}")
async def get_user(user_id: int):
    return {"user_id": user_id}

# Include in main app
app.include_router(users_router)
app.include_router(posts_router)

3. Input Validation

from pydantic import BaseModel, validator
from typing import Optional

class UserQuery(BaseModel):
    user_id: int
    include_posts: Optional[bool] = False

    @validator('user_id')
    def user_id_must_be_positive(cls, v):
        if v <= 0:
            raise ValueError('user_id must be positive')
        return v

@app.get("/users/{user_id}")
async def get_user(user_id: int, query: UserQuery = Depends()):
    # user_id is already validated by Pydantic
    return {"user_id": user_id}

Troubleshooting Checklist

When encountering 404 errors, check:

  1. Route Definition: Is the route properly defined with correct decorators?
  2. HTTP Method: Does the request method match the route method?
  3. Path Parameters: Are path parameters correctly formatted and typed?
  4. Router Inclusion: Are all routers included in the main app?
  5. Route Order: Are specific routes defined before generic ones?
  6. Case Sensitivity: URLs are case-sensitive in FastAPI
  7. Trailing Slashes: Handle both /users and /users/ if needed
  8. Documentation: Check /docs to see all available routes

External Resources