> ## Documentation Index
> Fetch the complete documentation index at: https://fastapi2day.codewithsiva.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Request Handling in FastAPI: Params, Bodies & Headers

> Master path parameters, query parameters, JSON request bodies, and HTTP headers in FastAPI with full type validation and the Annotated pattern.

When clients talk to your API, they have several different ways of sending you data: values embedded in the URL, key-value pairs in the query string, a JSON body in the request payload, and metadata in request headers. FastAPI gives you clean, declarative ways to handle all of them — and it validates every piece of incoming data automatically, so you never have to write manual type-checking code. This lesson walks through each channel and shows you how to use the powerful `Annotated` validation style recommended in Pydantic v2.

***

## Request Data Processing Flow

FastAPI intercepts incoming HTTP requests, extracts parameters from every part of the request, validates their types using Pydantic, and feeds the clean values directly into your route function.

```mermaid theme={null}
graph TD
    Client[Client HTTP Request] --> Route{FastAPI Router}

    Route -->|Path Params: /employees/42| PathVal[Verify integer type]
    Route -->|Query Params: ?dept=Sales| QueryVal[Verify string type]
    Route -->|Request Body: JSON payload| BodyVal[Validate with Pydantic Schema]

    PathVal & QueryVal & BodyVal -->|Validation Passes| Controller[Execute Route Function]
    PathVal & QueryVal & BodyVal -->|Validation Fails| Error[Return 422 Unprocessable Entity]
```

<Note>
  Validation happens **before** your function is ever called. If any input fails, FastAPI immediately returns a `422 Unprocessable Entity` with a detailed error message explaining exactly what went wrong.
</Note>

***

## Path Parameters

Path parameters are variables embedded directly inside the URL path. You define them using curly braces in the route, then add a matching function argument with a type annotation.

```python theme={null}
from fastapi import FastAPI

app = FastAPI()

# Sample mock database
EMPLOYEES = {
    1: {"name": "Alice", "department": "Engineering"},
    2: {"name": "Bob", "department": "Product"}
}

@app.get("/employees/{employee_id}")
def get_employee(employee_id: int):
    # FastAPI automatically validates that 'employee_id' is an integer
    employee = EMPLOYEES.get(employee_id)
    if not employee:
        return {"error": "Employee not found"}
    return employee
```

* Define the variable in the path inside curly braces: `/employees/{employee_id}`.
* Annotate `employee_id: int` in the function signature — if a user requests `/employees/abc`, FastAPI immediately returns `422` without you writing any checking code.

### Validated Path Parameters

Use `Path()` with `Annotated` to enforce additional constraints:

```python theme={null}
from typing import Annotated
from fastapi import Path

@app.get("/employees/{employee_id}")
def get_employee(
    employee_id: Annotated[int, Path(gt=0, description="Must be a positive employee ID")]
):
    return {"employee_id": employee_id}
```

***

## Query Parameters

Any function parameter that is **not** part of the path is automatically treated as a query parameter. They appear after the `?` in the URL.

```python theme={null}
@app.get("/employees")
def list_employees(department: str | None = None, limit: int = 10):
    results = list(EMPLOYEES.values())

    if department:
        results = [
            emp for emp in results
            if emp["department"].lower() == department.lower()
        ]

    return results[:limit]
```

Request:

```http theme={null}
GET /employees?department=Engineering&limit=5
```

* Use `str | None = None` for optional parameters.
* Provide a default value directly (e.g., `limit: int = 10`).

### Validated Query Parameters

```python theme={null}
from typing import Annotated
from fastapi import Query

@app.get("/employees")
def list_employees(
    page: Annotated[int, Query(ge=1, description="Page number")] = 1,
    limit: Annotated[int, Query(ge=1, le=100, description="Results per page")] = 20,
    department: Annotated[str | None, Query(alias="dept")] = None,
):
    return {"page": page, "limit": limit, "department": department}
```

The `alias="dept"` means clients send `?dept=Engineering` but your parameter is named `department` in Python.

***

## Request Bodies with Pydantic

When you need to send structured data — typically to create or update a resource — you use a **request body** with `POST`, `PUT`, or `PATCH`. Define the shape using a **Pydantic model**.

```python theme={null}
from pydantic import BaseModel, Field

class EmployeeCreate(BaseModel):
    name: str = Field(..., min_length=2, description="First and last name")
    department: str = Field(..., description="Assigned department")
    salary: float = Field(..., gt=0, description="Monthly base salary in USD")

@app.post("/employees")
def create_employee(employee: EmployeeCreate):
    new_id = max(EMPLOYEES.keys()) + 1 if EMPLOYEES else 1
    EMPLOYEES[new_id] = employee.model_dump()
    return {"id": new_id, "data": EMPLOYEES[new_id]}
```

FastAPI automatically:

1. Reads the request body as JSON
2. Validates it against `EmployeeCreate`
3. Creates an `EmployeeCreate` object and injects it as the `employee` parameter

Use `employee.model_dump()` to get a standard Python dictionary from the validated model.

### Recommended `Annotated` Style

In Pydantic v2, the preferred approach separates the type from the validation metadata:

```python theme={null}
from typing import Annotated
from pydantic import BaseModel, EmailStr, Field

class Employee(BaseModel):
    name: Annotated[str, Field(min_length=3, max_length=50)]
    age: Annotated[int, Field(gt=0, lt=100)]
    email: EmailStr
    salary: Annotated[float, Field(ge=0)]
```

Both styles work, but `Annotated` is the recommended approach going forward.

***

## Headers

You can read HTTP headers sent by clients using the `Header` class. FastAPI automatically converts `snake_case` parameter names to `kebab-case` header names.

```python theme={null}
from fastapi import Header

@app.get("/system-info")
def get_system_info(
    user_agent: str | None = Header(None),
    x_api_key: str | None = Header(None)
):
    return {
        "user_agent": user_agent,
        "api_key_sent": x_api_key is not None
    }
```

<Note>
  FastAPI maps `x_api_key` (Python snake\_case) to the `X-API-Key` header (HTTP kebab-case) automatically. You don't need to do anything special.
</Note>

***

## Validation Reference

### Common Validation Options

| Option        | Purpose                   |
| ------------- | ------------------------- |
| `gt`          | Greater than              |
| `ge`          | Greater than or equal     |
| `lt`          | Less than                 |
| `le`          | Less than or equal        |
| `min_length`  | Minimum string length     |
| `max_length`  | Maximum string length     |
| `pattern`     | Regular expression match  |
| `alias`       | Alternate parameter name  |
| `title`       | Display title in API docs |
| `description` | Description in API docs   |

### Useful Pydantic Types

| Type       | Purpose                        |
| ---------- | ------------------------------ |
| `EmailStr` | Validates email addresses      |
| `AnyUrl`   | Validates URLs                 |
| `UUID`     | Validates UUID values          |
| `date`     | Date only                      |
| `datetime` | Date and time                  |
| `Decimal`  | High-precision decimal numbers |

<Warning>
  `EmailStr` requires the `email-validator` package. Install it with:

  ```bash theme={null}
  pip install email-validator
  ```
</Warning>

***

## Putting It All Together

Here's a complete example combining path parameters, query parameters, a request body, and validation in one file:

```python theme={null}
from typing import Annotated
from fastapi import FastAPI, Path, Query
from pydantic import BaseModel, Field

app = FastAPI()

class ItemCreate(BaseModel):
    name: Annotated[str, Field(min_length=2, max_length=100)]
    price: Annotated[float, Field(gt=0)]
    in_stock: bool = True

ITEMS: dict = {}

@app.get("/items/{item_id}")
def get_item(
    item_id: Annotated[int, Path(gt=0)],
    include_details: bool = False
):
    return {"item_id": item_id, "details": include_details}

@app.get("/items")
def list_items(
    search: Annotated[str | None, Query(min_length=2)] = None,
    limit: Annotated[int, Query(ge=1, le=50)] = 10
):
    return {"search": search, "limit": limit}

@app.post("/items")
def create_item(item: ItemCreate):
    new_id = len(ITEMS) + 1
    ITEMS[new_id] = item.model_dump()
    return {"id": new_id, **ITEMS[new_id]}
```
