How to Model Reponses in FastAPI (3 Examples)

Updated: September 2, 2023 By: Khue Post a comment

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!