Here we will discuss on basic concepts which are essentials for the fastapi developer to understand the workflow of fastapi in real world applications.
Path and Query Parameters
Fastapi offers two ways to capture data from URLs in your application: path parameters and query parameters. They both server different purposes and have distinct way of being decleared in code.
Path Parameters:
- typically used to identify resources (eg: userid)
- it is designed to capture specific segment with in URL path.
- They act like variable that are filled in based on the corresponding part of URL.They are define using curly braces {} in route path.
-
eg: /users/{user_id}
Declaring in FastAPI:
use Path function from fastapi when defining your function arguments, you can aslo sepcify datatypes and descriptions.
Query Parameters:
- Used For Filtering,sorting and pagination
- it is used to capture optional key-value pair appended to the URL after question mark (?). they provide additional filtering or data specific to the request.
- You might have a api endpoint to get a list of user and you want to allow filtering by name, you could use query parameters like
/users?name=amrit
Declaring in fastapi
while not mandatory for query parameters, it's good pratice to use the query function from fastapi.
Example:
from fastapi import FastAPI,status
from fastapi.responses import JSONResponse
from database import list_of_student
app = FastAPI()
def all_users():
return list_of_student
def get_user_by_id(id,users):
for user in users:
if id==user.get('id'):
return user
return False
@app.get("/users")
async def get_all_user(name:str=None):
users=all_users()
if name:
users=[user for user in users if name in user['name']]
return JSONResponse(content={"data":users,"message":"Users List"},status_code=status.HTTP_200_OK)
@app.get("/user/{user_id}")
async def fetch_user(user_id:int,name:str=None):
result=get_user_by_id(user_id,all_users())
if result:
return JSONResponse(content={"data":result,"message":"User fetch Successfully"},status_code=status.HTTP_200_OK)
return JSONResponse(content={"data":result,"message":"User not found"},status_code=status.HTTP_404_NOT_FOUND)
Accessing Form Data
# pip install python-multipart
from fastapi import Form
@app.post("/submit-form")
async def submit_form(username:str=Form(...),password:str=Form()):
return {"username":username,"password":password}
working with file
from fastapi import FastAPI,File,UploadFile
from fastapi.responses import JSONResponse
import os
app=FastAPI()
UPLOAD_FOLDER='uploads'
os.makedirs(UPLOAD_FOLDER,exist_ok=True)
@app.post('/upload_file')
async def upload_file(file:UploadFile=File(...)):
#access uploaded file using UploadFile parameter
file_path=os.path.join(UPLOAD_FOLDER,file.filename)
with open (file_path,"wb") as buffer:
buffer.write(await file.read())
return JSONResponse(content={"message":"file uploaded successfully","filename":file.filename,"content_type":file.content_type})
for multiple file
@app.post('/upload_files')
async def upload_files(files: list[UploadFile] = File(...)):
uploaded_files_info = []
for file in files:
file_path = os.path.join(UPLOAD_FOLDER, file.filename)
with open(file_path, "wb") as buffer:
buffer.write(await file.read())
uploaded_files_info.append({
"filename": file.filename,
"content_type": file.content_type,
"file_path": file_path
})
return JSONResponse(
content={
"message": "Files uploaded successfully",
"uploaded_files": uploaded_files_info
}
)
Validation
query parameter validation
you can define validation directly ion the route handlers function using fast api's query parameter decorator.
from fastapi import Query
@app.get("/student")
def student(q:str=Query(None,min_length=1,max_length=5)):
return {"q":q}
path parameter validation
validation route can be applied directly with in the route path declaration.
from fastapi import Path
@app.get("/student/{student_id}")
def student(student_id:int=Path(...,title="Id Of the Student",gt=0)):
return {"Student_id":student_id}
Body Parameter Validation
For request Bodies , pydantic models are commonly used to define structure and validation rules.Validation is automatically done by pydantic
you can learn more about it from https://docs.pydantic.dev/latest/.
pydantic
is the data validation library in python, which i s widely used in fastapi for validating request and response data.
pydantic provides a way to define schemas using python classes, allowing you to specify the structure,type and validation rules for your data.
from pydantic import BaseModel,validator,Field, model_validator, ValidationError
from typing import Optional,Union,List
class Student(BaseModel):
name: str
age:int
address:str
country: Optional[str] = "Nepal"
no_of_subjects: int = Field(..., gt=5, description="Number of subjects")
hobbies_and_interests: Union[List[str], str] = Field(default_factory=list)
@validator("age")
def age_validation(cls, v):
if v<=0 or v>100:
raise ValueError("Age must be in between 1 to 100")
return v
@validator("name", pre=True, always=True)
def handle_empty_string(cls, value):
if value == "": # If the value is an empty string
return None
return value
@model_validator(mode="before")
@classmethod
def check_for_username_email(cls, values):
email = values.get("email")
username = values.get("username")
# Ensure email and username are not the same
if email and username and email.split('@')[0] == username:
raise ValueError("Email username part should not be the same as the username.")
return values # Must return modified values dictionary
More Info Related To Pydantic
from pydantic import BaseModel,Field,EmailStr
from datetime import datetime,timezone
class User(BaseModel):
name: str = Field(..., alias="username")
email:EmailStr
age:int
created_time: datetime | None = datetime.now(timezone.utc)
read_time: datetime | None = None
class Config:
'''
This allows you to create (or populate) the model using the field names,
even if you’ve set alias for the fields.
'''
allow_population_by_field_name = True
'''
This tells Pydantic how to serialize (.json(), .dict(), etc.)
specific types—in this case, datetime—into a JSON-friendly format
'''
# isoformt() will convert the datetime object into the string
json_encoders = {datetime: lambda v: v.isoformat()}
allow_population_by_field_name = True
By default, Pydantic expects the input dictionary to use the alias "username"
. But with allow_population_by_field_name = True
, you can also use the original Python name name
when creating the model:
User(name="panta", ...) # This works if allow_population_by_field_name = True
Without this setting, you’d be forced to use only "username"
when creating the object.
json_encoders = {datetime: lambda v: v.isoformat()}
This tells Pydantic how to serialize (.json()
, .dict()
, etc.) specific types—in this case, datetime
—into a JSON-friendly format.
In programming, serialization means converting a complex data object (like a Python object) into a format that can be easily stored or sent (e.g., as JSON, XML, or a string).
In the context of Pydantic:
When we say Pydantic serializes a model:
- It converts the model (a Python object) into a dictionary (
.dict()
) or a JSON string (.json()
). - During this process, non-primitive types like
datetime
,Decimal
, etc., need special handling to become string-compatible.
Example
from pydantic import BaseModel
from datetime import datetime, timezone
class Example(BaseModel):
timestamp: datetime
class Config:
json_encoders = {datetime: lambda v: v.isoformat()}
data = Example(timestamp=datetime.now(timezone.utc))
print(data.json())
Output:
{"timestamp": "2025-05-05T14:30:00+00:00"}
datetime.now(timezone.utc)
is a Python object..json()
serializes it into a string that is JSON-compatible using.isoformat()
.
Specifically, json_encoders
=
{datetime:
lambda
v
: v.
isoformat
()}
it's used when you call:
.json()
— converts the model to a JSON string..dict()
(withby_alias=True
or other options)..model_dump()
or.model_dump_json()
in Pydantic v2.- When using the model as a response in a FastAPI endpoint (because FastAPI internally calls
.json()
or similar).
It is not used when:
- You're just creating the model.
- You're saving data to a database (e.g., in SQLAlchemy).
- You're reading data and passing it into the model
Why it's needed:
Python datetime
objects are not JSON serializable by default. JSON supports strings, so we need to convert datetime to a string.
How it works:
This encoder uses datetime.isoformat()
to convert a datetime
object into an ISO 8601 formatted string (e.g., "2024-05-05T10:30:00+00:00"
).
Example:
from datetime import datetime, timezone
n = Notification(
customer_member_id="123",
notification_type=NotificationType.info,
message="New study available"
)
print(n.json())
With the encoder, the created_time
field (which is a datetime) is automatically converted to an ISO string in the output JSON.