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
- Swagger UI: http://127.0.0.1:8000/docs
- ReDoc: http://127.0.0.1:8000/redoc
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:
- http://127.0.0.1:8000/items/
- http://127.0.0.1:8000/items/?offset=0&count=10
- http://127.0.0.1:8000/items/?offset=20
Setting defaults makes parameters optional. Other types can use their default values or None:
int = 0,str = "",bool = Falseint | 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
- Parameters matching path variables are path parameters
- Single-type parameters (int, float, str, bool) are query parameters
- 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}