When building APIs, it’s crucial to have full control over what your endpoints return - not just for security, but for clarity and maintainability. FastAPI’s response_model
parameter helps you do exactly that. It defines the shape of the output using Pydantic models, allowing FastAPI to automatically validate and serialize responses.
We can take advantage of these characteristics in useful ways:
- Unnecessary fields can be excluded from the response data.
- The response structure can be clearly presented in the auto-generated documentation.
- Data type validation helps prevent bugs.
Basic Usage
In the example, we defined the following Pydantic models. In the response, we can see that the password
field which was not explicitly defined is excluded. Meanwhile, fields like description
, tax
, and tags
appear in the response with their default values, even though they weren’t included in the return statement.
Using get
1
2
3
4
5
6
7
8
9
10
| class Item(BaseModel):
name: str
description: Union[str, None] = None
price: float
tax: Union[float, None] = None
tags: List[str] = []
@router.get("/response_model/case1", response_model=Item)
async def read_items():
return {"name": "Portal Gun", "price": 42.0, "password": "1234"}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| try:
url = "http://127.0.0.1:7249/ds2man/basic/response_model/case1"
async with aiohttp.ClientSession(
trust_env=True, timeout=aiohttp.ClientTimeout(total=120)
) as session:
async with session.get(url, params=data) as response:
response.raise_for_status()
await log_response_info(response)
except ClientResponseError as e:
print(f"[ClientResponseError] {e.status}: {e.message}")
except Exception as e:
print(f"[Unhandled Exception] {e}")
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
| [Response Info]
Status : 200 OK
URL : http://127.0.0.1:7249/ds2man/basic/response_model/case1
Method : GET
Content-Type : application/json
Charset : None
Cookies : {}
Headers:
{'Content-Length': '74',
'Content-Type': 'application/json',
'Date': 'Sun, 04 May 2025 01:47:07 GMT',
'Server': 'uvicorn'}
OK? : True
JSON Body:
type(data): <class 'dict'>
{'description': None,
'name': 'Portal Gun',
'price': 42.0,
'tags': [],
'tax': None
}
|
Both List
and Dict
types are supported.
1
2
3
4
5
6
7
8
9
10
11
12
13
| class Item(BaseModel):
name: str
description: Union[str, None] = None
price: float
tax: Union[float, None] = None
tags: List[str] = []
@router.get("/response_model/case2", response_model=List[Item])
async def read_items():
return [
{"name": "Portal Gun", "price": 42.0},
{"name": "Plumbus", "price": 32.0},
]
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| try:
url = "http://127.0.0.1:7249/ds2man/basic/response_model/case2"
async with aiohttp.ClientSession(
trust_env=True, timeout=aiohttp.ClientTimeout(total=120)
) as session:
async with session.get(url, params=data) as response:
response.raise_for_status()
await log_response_info(response)
except ClientResponseError as e:
print(f"[ClientResponseError] {e.status}: {e.message}")
except Exception as e:
print(f"[Unhandled Exception] {e}")
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
| [Response Info]
Status : 200 OK
URL : http://127.0.0.1:7249/ds2man/basic/response_model/case2
Method : GET
Content-Type : application/json
Charset : None
Cookies : {}
Headers:
{'Content-Length': '148',
'Content-Type': 'application/json',
'Date': 'Sun, 04 May 2025 02:08:47 GMT',
'Server': 'uvicorn'}
OK? : True
JSON Body:
type(data): <class 'list'>
[{'description': None,
'name': 'Portal Gun',
'price': 42.0,
'tags': [],
'tax': None},
{'description': None,
'name': 'Plumbus',
'price': 32.0,
'tags': [],
'tax': None}
]
|
Using post
1
2
3
4
5
6
7
8
9
10
| class Item(BaseModel):
name: str
description: Union[str, None] = None
price: float
tax: Union[float, None] = None
tags: List[str] = []
@router.post("/response_model/case3", response_model=Item)
async def create_item(item: Item):
return item
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
| try:
url = "http://127.0.0.1:7249/ds2man/basic/response_model/case3"
data = {
"description": "Hello",
"name": "DS2Man",
"price": 100.1,
"tags": ["FastAPI", "Response Model"]
}
async with aiohttp.ClientSession(
trust_env=True, timeout=aiohttp.ClientTimeout(total=120)
) as session:
async with session.post(url, json=data) as response:
response.raise_for_status()
await log_response_info(response)
except ClientResponseError as e:
print(f"[ClientResponseError] {e.status}: {e.message}")
except Exception as e:
print(f"[Unhandled Exception] {e}")
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
| [Response Info]
Status : 200 OK
URL : http://127.0.0.1:7249/ds2man/basic/response_model/case3
Method : POST
Content-Type : application/json
Charset : None
Cookies : {}
Headers:
{'Content-Length': '100',
'Content-Type': 'application/json',
'Date': 'Sun, 04 May 2025 02:04:21 GMT',
'Server': 'uvicorn'}
OK? : True
JSON Body:
type(data): <class 'dict'>
{'description': 'Hello',
'name': 'DS2Man',
'price': 100.1,
'tags': ['FastAPI', 'Response Model'],
'tax': None
}
|