The Fundamentals
FastAPI is a fast-growing Python web framework that can help you build high-performance backend projects. One of the features of the web framework is that it allows you to declare the type of response data using Pydantic models. This brings to the table several benefits, such as:
- It validates the returned data and ensures that it matches the expected shape and format. This can help you catch errors and bugs in your code before they cause problems for your clients.
- It generates a JSON Schema for the response, which is included in the OpenAPI documentation. This can help your clients understand what kind of data they will receive and how to use it. It can also enable automatic client code generation tools.
- It filters and limits the output data to what is defined in the response model. This can improve performance and security, as you only send the necessary data and avoid exposing sensitive information.
There are 2 different ways to declare your response model. The first approach is to use the return type annotation of your path operation function, and the second one is to use the response_model
parameter of the path operation decorator.
Using the response_model parameter
The response_model
parameter is an argument that you can pass to the path operation decorator, such as @app.get
, @app.post
, @app.put
, etc. The value of the response_model
parameter should be a Pydantic model class that defines the shape and format of the response data. In general, there are two steps to follow:
# Step 1: Declare the response model class
class MyResponseModel(BaseModel):
name: str
# add other fields here
# Step 2: Use the response model in the path operation decorator
@app.get("/some-route", response_model=MyResponseModel)
async def get_data():
# Fetch data from the database or some other source
# Make sure it is in the format of the response model
data = MyResponseModel(name="Sling Academy")
# Return the data
return data
Using the return type annotation
The return type annotation is a way of specifying the type of data that a function returns, using the ->
symbol after the function parameters. You can use the return type annotation with your path operation functions in FastAPI, to declare the type of the response data. The value of the return type annotation should be a Pydantic model class that defines the shape and format of the response data. In most cases, you will do like this:
# Step 1: Declare the response model
class MyResponseModel(BaseModel):
name: str
# add other fields here
@app.get("/some-route")
# Step 2: Specify the response model as return type
async def get_data() -> MyResponseModel:
# Fetch data from the database or some other source
# Make sure it is in the format of the response model
data = MyResponseModel(name="Sling Academy")
# Return the data
return data
Complete Examples
Let’s see some real-world examples of modeling responses in FastAPI. These ones are arranged in order from basic to advanced, from simple to complex.
Get a user by ID (basic)
This example shows how to create a simple endpoint that returns a user
object based on a given ID. The user
object has 2 attributes: name
and age
. The endpoint uses a path parameter to get the ID from the URL and validates it as an integer. The endpoint also uses a response model to specify the shape of the data returned by the API.
The code:
from fastapi import FastAPI, Path
from pydantic import BaseModel
# Define the user object as a Pydantic model
class User(BaseModel):
name: str
age: int
# Create an app instance
app = FastAPI()
# Create an endpoint that returns a user object by ID
@app.get("/users/{user_id}", response_model=User)
def get_user(user_id: int = Path(...)):
# Return a user object that matches the response model
return User(name="Sling Academy", age=88)
Get a list of products (intermediate)
This example demonstrates how to create an endpoint that returns a list of product
objects. The product
object has 3 attributes: id
, name
, and price
. The endpoint uses a query parameter to get the limit of products to return and validates it as an optional integer with a default value of 10. The example also uses a response model to specify the shape of the data returned by the API and wraps it in a list type.
Here’s the code:
from fastapi import FastAPI, Query
from pydantic import BaseModel
from typing import List
# Define the product object as a Pydantic model
class Product(BaseModel):
id: int
name: str
price: float
# Create an app instance
app = FastAPI()
# Create an endpoint that returns a list of product objects
@app.get("/products", response_model=List[Product])
def get_products(limit: int = Query(10)):
# Return a list of product objects that matches the response model
products = []
# Create some dummy products
for i in range(limit):
products.append(Product(id=i + 1, name=f"Product {i+1}", price=(i + 1) * 10))
return products
Nested response models (advanced)
In this final example, we’ll make an endpoint that returns a nested object that contains other objects. The nested object is an order
object that has 4 attributes: id
, date
, total
, and items
. The items
attribute is a list of item
objects that have 2 attributes: product
and quantity
. The endpoint uses a path parameter to get the order ID from the URL and validates it as an integer. We also use a response model to specify the shape of the data returned by the API and nest other models inside it. Below is the full source code:
from fastapi import FastAPI, Path
from pydantic import BaseModel
from typing import List
from datetime import date
# Define the product object as a Pydantic model
class Product(BaseModel):
id: int
name: str
price: float
# Define the item object as a Pydantic model that contains a product object
class Item(BaseModel):
product: Product
quantity: int
# Define the order object as a Pydantic model that contains a list of item objects
class Order(BaseModel):
id: int
date: date
total: float
items: List[Item]
# Create an app instance
app = FastAPI()
# Create an endpoint that returns an order object by ID
@app.get("/orders/{order_id}", response_model=Order)
def get_order(order_id: int = Path(...)):
# Return an order object that matches the response model
return Order(
id=order_id,
date=date.today(),
total=100.0,
items=[
Item(product=Product(id=1, name="Product 1", price=10.0), quantity=2),
Item(product=Product(id=2, name="Product 2", price=20.0), quantity=3),
],
)
Conclusion
We’ve gone through 2 different ways to declare response models in FastAPI. We’ve also examined some examples that applying the knowledge we’ve learned in practice. At this point, you’ve just taken one more step forward in building robust and secure APIs with FastAPI.
This tutorial ends here. If you find something outdated or incorrect, please let me know by leaving a comment. Happy coding & have a nice day!