FastAPI Parameter Handling and Request Body Validation

Hello World Example

The traditional Hello World implementation.

Installation

pip install fastapi
pip install "uvicorn[standard]"

main.py

from typing import Union
from fastapi import FastAPI

server = FastAPI()

@server.get("/")
def root_endpoint():
    return {"message": "Hello World"}

@server.get("/items/{item_identifier}")
def get_item(item_identifier: int, query_param: Union[str, None] = None):
    return {"id": item_identifier, "query": query_param}

Execution

uvicorn main:server --reload

Interactive Documentation

Parameter Types

Path Parameters

from fastapi import FastAPI

server = FastAPI()

@server.get("/items/{item_identifier}")
async def fetch_item(item_identifier):
    return {"identifier": item_identifier}

Type Declaration for Path Parameters
from fastapi import FastAPI

server = FastAPI()

@server.get("/items/{item_identifier}")
async def fetch_item(item_identifier: int):
    return {"identifier": item_identifier}

Data Type Conversion
from fastapi import FastAPI

server = FastAPI()

@server.get("/items/{item_identifier}")
async def fetch_item(item_identifier: int):
    return {"identifier": item_identifier}

When accessing http://127.0.0.1:8000/items/3, the response will be:

{"identifier": 3}

Order Significance

For URLs like /users/me and /users/{user_id}, route handlers execute in sequence. Declare /users/me before /users/{user_id}:

from fastapi import FastAPI

server = FastAPI()

@server.get("/users/me")
async def current_user():
    return {"user_id": "current_user"}

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

Otherwise, /users/{user_id} would match /users/me, treating "me" as the user_id value.

Query Parameters (URL Parameters)

Parameters not defined in the path are automatically treated as query parameters. Priority: Path parameters > Query parameters.

Default Values | Optional vs Required Parameters
from fastapi import FastAPI

server = FastAPI()

mock_database = [{"name": "Alpha"}, {"name": "Beta"}, {"name": "Gamma"}]

@app.get("/items/")
async def list_items(offset: int = 0, count: int = 10):
    return mock_database[offset : offset + count]

All these requests are equivalent:

Setting defaults makes parameters optional. Other types can use their default values or None:

  • int = 0, str = "", bool = False
  • int | None = None, str | None = None, bool | None = None
Mandatory Parameters

Parameters without default values are required.

Using Query as Default Value
from typing import Union
from fastapi import FastAPI, Query

server = FastAPI()

@app.get("/items/")
async def search_items(search_term: Union[str, None] = Query(default=None, max_length=50)):
    results = {"items": [{"id": "Alpha"}, {"id": "Beta"}]}
    if search_term:
        results.update({"search": search_term})
    return results

Enhanced Query Validation
from typing import Union
from fastapi import FastAPI, Query

server = FastAPI()

@app.get("/items/")
async def search_items(
    search_term: Union[str, None] = Query(
        default=None,
        min_length=3,
        max_length=50,
        alias="alternate_name",
        title="Search Term Title",
        description="Search term description",
        deprecated=True,
        pattern=r"^[a-zA-Z]+$"
    )
):
    results = {"items": [{"id": "Alpha"}, {"id": "Beta"}]}
    if search_term:
        results.update({"search": search_term})
    return results

Request Body

Creating Data Models & Declaring Request Bodies
from fastapi import FastAPI
from pydantic import BaseModel

class Product(BaseModel):
    title: str
    details: str | None = None
    cost: float
    vat: float | None = None

server = FastAPI()

@app.post("/products/")
async def add_product(product: Product):
    return product

Parameter Recognition Rules
  1. Parameters matching path variables are path parameters
  2. Single-type parameters (int, float, str, bool) are query parameters
  3. Pydantic model parameters are request bodies
Validating Request Body Fields
from typing import Annotated
from fastapi import Body, FastAPI
from pydantic import BaseModel, Field

server = FastAPI()

class Product(BaseModel):
    title: str
    details: str | None = Field(
        default=None, 
        title="Product Details", 
        max_length=300
    )
    cost: float = Field(gt=0, description="Price must exceed zero")
    vat: float | None = None

@app.put("/products/{product_id}")
async def modify_product(product_id: int, product: Annotated[Product, Body(embed=True)]):
    return {"id": product_id, "data": product}

Multiple Request Bodies & Single Values in Request Body
from typing import Annotated
from fastapi import Body, FastAPI
from pydantic import BaseModel

server = FastAPI()

class Product(BaseModel):
    title: str
    details: str | None = None
    cost: float
    vat: float | None = None

class Customer(BaseModel):
    username: str
    full_name: str | None = None

@app.put("/products/{product_id}")
async def update_product(
    product_id: int, 
    product: Product,
    customer: Customer,
    priority: Annotated[int, Body()]
):
    return {
        "id": product_id, 
        "product": product, 
        "customer": customer, 
        "priority": priority
    }

Expected Request Body

{
    "product": {
        "title": "Widget",
        "details": "Premium quality",
        "cost": 25.0,
        "vat": 2.0
    },
    "customer": {
        "username": "john_doe",
        "full_name": "John Doe"
    },
    "priority": 3
}

Data Models

Embedding Single Request Bodies

from typing import Annotated
from fastapi import Body, FastAPI
from pydantic import BaseModel

server = FastAPI()

class Product(BaseModel):
    title: str
    details: str | None = None
    cost: float
    vat: float | None = None

@app.put("/products/{product_id}")
async def update_product(
    product_id: int,
    product: Annotated[Product, Body(embed=True)]
):
    return {"id": product_id, "data": product}

Expected Request Body

{
    "product": {
        "title": "Gadget",
        "details": "Advanced features",
        "cost": 45.0,
        "vat": 3.6
    }
}

Nested Models & Lists

from typing import List, Union
from fastapi import FastAPI
from pydantic import BaseModel, HttpUrl

server = FastAPI()

class Media(BaseModel):
    link: HttpUrl
    label: str

class Product(BaseModel):
    title: str
    details: str | None = None
    cost: float
    vat: float | None = None
    categories: set[str] = set()
    media_files: list[Media] | None = None

@app.put("/products/{product_id}")
async def update_product(product_id: int, product: Product):
    return {"id": product_id, "data": product}

Expected Request Body

{
    "title": "Super Gadget",
    "details": "Next generation device",
    "cost": 99.99,
    "vat": 8.0,
    "categories": ["electronics", "gadgets"],
    "media_files": [
        {
            "link": "http://example.com/image1.jpg",
            "label": "Front View"
        },
        {
            "link": "http://example.com/image2.jpg",
            "label": "Side View"
        }
    ]
}

Multi-level Nested Models

from fastapi import FastAPI
from pydantic import BaseModel, HttpUrl

server = FastAPI()

class Media(BaseModel):
    link: HttpUrl
    label: str

class Product(BaseModel):
    title: str
    details: str | None = None
    cost: float
    vat: float | None = None
    categories: set[str] = set()
    media_files: list[Media] | None = None

class Catalog(BaseModel):
    name: str
    description: str | None = None
    budget: float
    products: list[Product]

@app.post("/catalogs/")
async def create_catalog(catalog: Catalog):
    return catalog

Adding Metadata to Models

from fastapi import FastAPI
from pydantic import BaseModel

server = FastAPI()

class Product(BaseModel):
    title: str
    details: str | None = None
    cost: float
    vat: float | None = None

    model_config = {
        "json_schema_extra": {
            "examples": [
                {
                    "title": "Sample Product",
                    "details": "High-quality item",
                    "cost": 29.99,
                    "vat": 2.4
                }
            ]
        }
    }

@app.put("/products/{product_id}")
async def update_product(product_id: int, product: Product):
    return {"id": product_id, "data": product}

Tags: FastAPI python pydantic api-development request-validation

Posted on Sat, 05 Sep 2026 16:34:09 +0000 by kr9091