FastAPI Basics • Lesson 8

Body - Fields

Learn to add validation and metadata to Pydantic model fields using Field(). Master field-level constraints, descriptions, and advanced validation.

🎯 What You'll Learn

  • Import and use Field from pydantic for model attributes
  • Add validation constraints to model fields (gt, max_length, etc.)
  • Include metadata like titles and descriptions for better documentation
  • Understand the relationship between Field, Query, Path, and Body

Body - Fields

🎯 What You'll Learn

This lesson follows the official FastAPI "Body - Fields" tutorial exactly. You'll learn how to use Pydantic's Field() function to add validation and metadata to model attributes, making your APIs more robust and well-documented.

By the end of this lesson, you'll understand how to:

  • Import and use Field from pydantic for model attributes
  • Add validation constraints to model fields (gt, max_length, etc.)
  • Include metadata like titles and descriptions for better documentation
  • Understand the relationship between Field, Query, Path, and Body

📚 Import Field

First, you need to import Field from pydantic:

from typing import Union
from fastapi import Body, FastAPI
from pydantic import BaseModel, Field

⚠️ Important: Notice that Field is imported directly from pydantic, not from fastapi like Query, Path, and Body.

🔧 Declare Model Attributes with Field

You can use Field with model attributes to add validation and metadata:

class Item(BaseModel):
    name: str
    description: Union[str, None] = Field(
        default=None, title="The description of the item", max_length=300
    )
    price: float = Field(gt=0, description="The price must be greater than zero")
    tax: Union[float, None] = None

Field Parameters

Field works the same way as Query, Path, and Body - it accepts the same validation parameters:

  • Validation constraints: gt, ge, lt, le, min_length, max_length, etc.
  • Metadata: title, description, example, etc.
  • Default values: default, default_factory

🎯 Complete Example

Following the official FastAPI tutorial:

from typing import Union
from fastapi import Body, FastAPI
from pydantic import BaseModel, Field

app = FastAPI()

class Item(BaseModel):
    name: str
    description: Union[str, None] = Field(
        default=None, title="The description of the item", max_length=300
    )
    price: float = Field(gt=0, description="The price must be greater than zero")
    tax: Union[float, None] = None

@app.put("/items/{item_id}")
async def update_item(item_id: int, item: Item = Body(embed=True)):
    results = {"item_id": item_id, "item": item}
    return results

Request Body Structure

With Body(embed=True), the request expects:

{
    "item": {
        "name": "Laptop",
        "description": "High-performance gaming laptop",
        "price": 999.99,
        "tax": 99.99
    }
}

🌟 Key Concepts

Field vs Function Parameters

Notice the similarity between model attributes with Field and function parameters:

Model attribute with Field:

price: float = Field(gt=0, description="The price must be greater than zero")

Function parameter with Path:

item_id: int = Path(gt=0, description="The ID of the item")

Both follow the same pattern: parameter: type = ValidationFunction(constraints)

Validation Benefits

Field validation provides:

  • Automatic validation: Invalid data returns HTTP 422 with detailed error messages
  • Type conversion: Automatic conversion between compatible types
  • Documentation: Metadata appears in OpenAPI schema and interactive docs
  • IDE support: Better autocomplete and type checking

Technical Details

  • Query, Path, Body create objects of subclasses of Param class
  • Param is a subclass of Pydantic's FieldInfo class
  • Field returns an instance of FieldInfo directly
  • All use the same underlying validation system

💡 Best Practices

Validation Constraints

class Item(BaseModel):
    name: str = Field(min_length=1, max_length=100)
    price: float = Field(gt=0, le=1000000)  # Positive, reasonable max
    quantity: int = Field(ge=1, le=1000)    # At least 1, reasonable max
    email: str = Field(regex=r'^[\w\.-]+@[\w\.-]+\.\w+$')

Descriptive Metadata

class User(BaseModel):
    username: str = Field(
        title="Username",
        description="Unique identifier for the user",
        min_length=3,
        max_length=50,
        example="johndoe"
    )
    age: int = Field(
        title="Age", 
        description="Age in years",
        ge=0,
        le=150,
        example=25
    )

Optional Fields with Defaults

class Item(BaseModel):
    name: str
    description: Union[str, None] = Field(
        default=None,
        title="Item Description", 
        max_length=500
    )
    is_active: bool = Field(default=True, description="Whether item is active")

🔗 Add Extra Information

You can include additional metadata in Field that will appear in the generated JSON Schema:

class Item(BaseModel):
    name: str = Field(
        title="Item Name",
        description="The name of the item",
        example="Laptop"
    )
    price: float = Field(
        gt=0,
        description="Price in USD",
        example=999.99
    )

⚠️ Warning: Extra keys passed to Field will be present in the OpenAPI schema. Some OpenAPI tools may not work with non-standard keys.

🎯 Validation Examples

Valid Request

{
    "item": {
        "name": "Gaming Laptop",
        "description": "High-performance laptop for gaming",
        "price": 1299.99
    }
}

Result: Success - all validations pass

Invalid Requests

Negative price:

{
    "item": {
        "name": "Laptop",
        "price": -100
    }
}

Result: HTTP 422 - "ensure this value is greater than 0"

Description too long:

{
    "item": {
        "name": "Laptop", 
        "description": "x".repeat(400),  // Over 300 characters
        "price": 999.99
    }
}

Result: HTTP 422 - "ensure this value has at most 300 characters"

🔗 What's Next?

In the next lesson, you'll learn about Body - Nested Models, where you'll discover how to create complex nested Pydantic models with lists, sets, and deeply nested structures.

📖 Additional Resources

💡 Hint

Import Field from pydantic (not fastapi). Use Field() to add validation like gt=0 for positive numbers and max_length for strings.

Ready to Practice?

Now that you understand the theory, let's put it into practice with hands-on coding!

Start Interactive Lesson