> ## 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.

# FastAPI Data Validation and Pydantic Models in Python

> Learn how to separate request, internal, and response models in FastAPI to validate all incoming data and safely filter what clients receive.

FastAPI is designed around one core principle: **validate all incoming data, process it internally, and return only the data that clients should see.** This lesson builds a realistic Employee Management System that shows exactly why you need three distinct Pydantic models — one for what clients send, one for what your application works with internally, and one for what you expose in responses. By the end, you'll understand how `Field()`, `Query()`, and `Path()` validators all fit together, and when to reach for the `Annotated` style.

***

## The Three-Model Pattern

A real application keeps incoming data, internal business data, and outgoing response data strictly separated:

```mermaid theme={null}
graph LR

A["Client\n\nPOST /employees"] --> B["EmployeeCreate\n\n(Request Model)"]
B --> C["Business Logic"]
C --> D["EmployeeInternal\n\n(Application Model)"]
D --> E["Save to Database"]
D --> F["EmployeeResponse\n\n(Response Model)"]
F --> G["Client Response"]
```

| Stage               | Model              | Purpose                                      |
| ------------------- | ------------------ | -------------------------------------------- |
| Incoming request    | `EmployeeCreate`   | Validates what the client is allowed to send |
| Internal processing | `EmployeeInternal` | Represents the complete working object       |
| Outgoing response   | `EmployeeResponse` | Exposes only safe, relevant fields           |

***

## Request Processing Flow

Every request passes through multiple validation stages before reaching your business logic:

```mermaid theme={null}
graph LR

A[Client Request] --> B[Path Validation]
A --> C[Query Validation]
A --> D[Request Model Validation]

B --> E[Business Logic]
C --> E
D --> E

E --> F[Internal Model]
F --> G[Response Model]
G --> H[Client Response]
```

***

## 1. Path Parameter Validation

Path parameters identify a specific resource. Add `Path()` constraints using `Annotated`:

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

app = FastAPI()

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

FastAPI validates that:

* The value can be converted to `int`
* It is greater than zero (`gt=0`)

If the client sends `/employees/abc` or `/employees/-5`, FastAPI returns a `422` error automatically — your function never runs.

***

## 2. Query Parameter Validation

Query parameters filter, search, sort, or paginate results:

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

@app.get("/employees")
def list_employees(
    department: str | None = None,
    limit: Annotated[int, Query(ge=1, le=100, description="Max results to return")] = 10
):
    return {"department": department, "limit": limit}
```

Request:

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

***

## 3. Request Body Validation

When clients create or update resources, they send JSON in the request body:

```http theme={null}
POST /employees
```

```json theme={null}
{
    "name": "Alice",
    "department": "Engineering",
    "salary": 8500
}
```

Create a **Request Model** to validate it:

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

class EmployeeCreate(BaseModel):
    name: str = Field(..., min_length=2, description="Full name of the employee")
    department: str
    salary: float = Field(..., gt=0, description="Monthly salary in USD")

@app.post("/employees")
def create_employee(employee: EmployeeCreate):
    return employee.model_dump()
```

FastAPI automatically reads the JSON body, validates it, creates an `EmployeeCreate` instance, and passes it to your function. If validation fails, `422` is returned immediately.

***

## 4. Why Isn't the Request Model Enough?

The request model represents **only what the client is allowed to send**. But your application usually needs to generate additional data automatically — data that should never come from the client:

* Employee ID
* Employee Code
* Tax ID
* Joining Date
* Created Timestamp

That's why you need a separate internal model.

***

## 5. Internal Model

After validation, your application creates its own complete working object:

```python theme={null}
from datetime import datetime
from pydantic import BaseModel

class EmployeeInternal(BaseModel):
    id: int
    name: str
    department: str
    salary: float
    employee_code: str
    tax_id: str
    joined_at: datetime

@app.post("/employees")
def create_employee(employee: EmployeeCreate):
    internal_employee = EmployeeInternal(
        id=101,
        name=employee.name,
        department=employee.department,
        salary=employee.salary,
        employee_code="EMP-101",
        tax_id="TAX123",
        joined_at=datetime.now()
    )
    # Save internal_employee to the database
    return {"message": "Employee created successfully"}
```

The client never sends `id`, `employee_code`, `tax_id`, or `joined_at` — these are generated by your application logic.

***

## 6. Response Model

The application should not expose its internal model directly. Create a **Response Model** containing only the fields that clients should receive:

```python theme={null}
class EmployeeResponse(BaseModel):
    id: int
    name: str
    department: str

@app.post("/employees", response_model=EmployeeResponse)
def create_employee(employee: EmployeeCreate):
    internal_employee = EmployeeInternal(
        id=101,
        name=employee.name,
        department=employee.department,
        salary=employee.salary,
        employee_code="EMP-101",
        tax_id="TAX123",
        joined_at=datetime.now()
    )
    return internal_employee  # FastAPI filters to EmployeeResponse fields only
```

Although `EmployeeInternal` contains `salary`, `employee_code`, `tax_id`, and `joined_at`, the client only receives:

```json theme={null}
{
    "id": 101,
    "name": "Alice",
    "department": "Engineering"
}
```

***

## Validation Tools Reference

### `Field()` — Request Body Validation

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

class Student(BaseModel):
    name: Annotated[str, Field(min_length=3, max_length=50)]
    age: Annotated[int, Field(gt=0, lt=100)]
```

### `Query()` — Query Parameter Validation

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

limit: Annotated[int, Query(ge=1, le=100)] = 20
```

### `Path()` — Path Parameter Validation

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

employee_id: Annotated[int, Path(gt=0)]
```

### 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       |
| `alias`       | Alternate parameter name |
| `description` | Description in API docs  |

***

## `Annotated` — Recommended Style

In Pydantic v2, the recommended way to write validation is with `Annotated`:

```python theme={null}
Annotated[type, validation]
```

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

age: Annotated[int, Field(gt=0, lt=100)]
```

The older syntax (`age: int = Field(gt=0, lt=100)`) still works, but `Annotated` is the preferred style going forward.

***

## `model_config` and `from_attributes`

When your API needs to return data from a database ORM (like SQLAlchemy), Pydantic needs to read attribute values from an object rather than a dictionary. Configure this with:

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

class EmployeeResponse(BaseModel):
    model_config = ConfigDict(from_attributes=True)

    id: int
    name: str
    department: str
```

Without `from_attributes=True`, Pydantic reads values using dictionary keys (`data["name"]`). With it, Pydantic reads from object attributes (`data.name`) — which is how ORM objects work.

Use `model_validate()` to convert an ORM object to your response model:

```python theme={null}
# Works with both dicts and ORM objects
response = EmployeeResponse.model_validate(orm_employee_object)
```

| Pydantic v1                     | Pydantic v2                                       |
| ------------------------------- | ------------------------------------------------- |
| `class Config: orm_mode = True` | `model_config = ConfigDict(from_attributes=True)` |

<Note>
  **Best Practice:** Use separate models for Request, Internal, and Response. Each has one responsibility — validation, processing, and safe exposure respectively. This keeps your application secure, flexible, and easy to maintain.
</Note>
