What is a Request Object?
The Request
object in FastAPI represents the HTTP request sent by the client. It contains metadata and payload(the actual data that the client sends to the server), such as:
- HTTP method (
GET
, POST
, PUT
, DELTE
, OPTIONS
, PATCH
, etc) - Query parameters
- Request headers
- Cookies
- Request body
- Client IP
- Path parameters
- App context (shared state)
Key Attributes of Request
Attribute | Description |
---|
request.method | HTTP method used for the request (e.g., GET , POST , PUT , DELETE , OPTIONS ) |
request.url | Full URL of the incoming request (e.g., http://localhost:9248/ds2man/basic/task/?question=Hello&user_name=DS2Man ) |
request.base_url | Base URL excluding the path and query string (e.g., http://localhost:9248/ ) |
request.headers | All HTTP headers (case-insensitive multi-dict) |
request.query_params | Query parameters parsed as a dictionary-like object (e.g., question=Hello&user_name=DS2Man ) |
request.path_params | Path parameters extracted from the route (e.g., /items/{item_id} → {"item_id": 123} ) |
request.cookies | Cookies included in the request, as a dictionary |
request.client | Client’s IP address and port, e.g., (e.g., Address(host=’127.0.0.1’, port=10519) |
request.scope | Low-level ASGI connection information (type, headers, path, etc.) - used internally by Starlette |
request.state | Per-request mutable state for sharing data between middlewares and routes (e.g., request.state.user ) |
await request.body() | Entire request body as raw bytes |
await request.json() | Parsed JSON body as a dictionary |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
| @router.get("/request&response/case1")
async def debug_request(request: Request):
visible_attrs = []
for key in dir(request):
if key.startswith("_"): # Exclude attributes starting with __ (e.g., __len__)
continue
try:
value = getattr(request, key)
# callable: Checks whether the object can be called like a function
if not callable(value): # Exclude methods/functions
visible_attrs.append(key)
except Exception:
# Skip attributes like request.auth that may raise errors when accessed
continue
result = {
"visible_keys": visible_attrs
}
return result
|
1
2
3
4
5
6
7
8
9
| url = "http://127.0.0.1:7249/ds2man/basic/request&response/case1"
async with aiohttp.ClientSession(
trust_env=True, timeout=aiohttp.ClientTimeout(total=120)
) as session:
async with session.get(url) as response:
data = await response.json()
print(f"\nJSON Body:")
print(f"type(data): {type(data)}")
pprint(data)
|
1
2
3
| JSON Body:
type(data): <class 'dict'>
{'visible_keys': ['base_url', 'client', 'cookies', 'headers', 'method', 'path_params', 'query_params', 'scope', 'state', 'url']}
|
Example - Using GET
1
2
3
4
5
6
7
8
9
10
| @router.get("/request&response/case2")
async def get_request_info(request: Request):
result = {
"method": request.method,
"client": request.client.host,
"headers": dict(request.headers),
"query": dict(request.query_params)
}
return result
|
1
2
3
4
5
6
7
8
9
| url = "http://127.0.0.1:7249/ds2man/basic/request&response/case2"
async with aiohttp.ClientSession(
trust_env=True, timeout=aiohttp.ClientTimeout(total=120)
) as session:
async with session.get(url) as response:
data = await response.json()
print(f"\nJSON Body:")
print(f"type(data): {type(data)}")
pprint(data)
|
1
2
3
4
5
6
7
8
9
| JSON Body:
type(data): <class 'dict'>
{'client': '127.0.0.1',
'headers': {'accept': '*/*',
'accept-encoding': 'gzip, deflate',
'host': '127.0.0.1:7249',
'user-agent': 'Python/3.11 aiohttp/3.11.11'},
'method': 'GET',
'query': {}}
|
Example - Using POST
1
2
3
4
5
6
7
8
9
10
11
12
| @router.post("/request&response/case3")
async def get_request_info(request: Request):
data = await request.json()
result = {
"method": request.method,
"client": request.client.host,
"headers": dict(request.headers),
"query": dict(request.query_params),
"body": data
}
return result
|
1
2
3
4
5
6
7
8
9
10
11
12
13
| url = "http://127.0.0.1:7249/ds2man/basic/request&response/case3"
payload = {
"key1": "value1",
"key2": "value2"
}
async with aiohttp.ClientSession(
trust_env=True, timeout=aiohttp.ClientTimeout(total=120)
) as session:
async with session.post(url, json=payload) as response:
data = await response.json()
print(f"\nJSON Body:")
print(f"type(data): {type(data)}")
pprint(data)
|
1
2
3
4
5
6
7
8
9
10
11
12
| JSON Body:
type(data): <class 'dict'>
{'body': {'key1': 'value1', 'key2': 'value2'},
'client': '127.0.0.1',
'headers': {'accept': '*/*',
'accept-encoding': 'gzip, deflate',
'content-length': '36',
'content-type': 'application/json',
'host': '127.0.0.1:7249',
'user-agent': 'Python/3.11 aiohttp/3.11.11'},
'method': 'POST',
'query': {}}
|
What is a Response Object?
A Response
object defines what the API sends back to the client. FastAPI handles most responses automatically by serializing the returned values, but for more control (e.g., setting custom headers, content type, or cookies), you can use the Response
object directly.
FastAPI uses JSONResponse
by default to automatically convert responses into JSON. Additionally, it provides other response classes such as HTMLResponse
and RedirectResponse
, which will be covered in detail in a later post.
There are several types of responses:
JSONResponse
(default)HTMLResponse
PlainTextResponse
StreamingResponse
RedirectResponse
FileResponse
1
2
3
4
5
6
7
8
| @router.get("/request&response/case4")
def set_custom_header(response: Response):
response.headers["X-Custom-Header"] = "FastAPI Rocks"
result = {
"message": "Header set"
}
return result
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| url = "http://127.0.0.1:7249/ds2man/basic/request&response/case4"
payload = {
"key1": "value1",
"key2": "value2"
}
async with aiohttp.ClientSession(
trust_env=True, timeout=aiohttp.ClientTimeout(total=120)
) as session:
async with session.get(url) as response:
print(f"Headers:")
pprint(dict(response.headers))
data = await response.json()
print(f"\nJSON Body:")
print(f"type(data): {type(data)}")
pprint(data)
|
1
2
3
4
5
6
7
8
9
10
11
| Headers:
{'Content-Length': '24',
'Content-Type': 'application/json',
'Date': 'Tue, 06 May 2025 00:02:57 GMT',
'Server': 'uvicorn',
'x-custom-header': 'FastAPI Rocks' # adding
}
JSON Body:
type(data): <class 'dict'>
{'message': 'Header set'}
|
Using Request and Response Together
Combining both Request
and Response
allows for complete control over the full request-response lifecycle:
1
2
3
4
5
6
7
8
9
| @router.post("/request&response/case5")
async def echo(request: Request, response: Response):
data = await request.json()
response.headers["X-Echoed"] = "True"
result = {
"received": data
}
return result
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| url = "http://127.0.0.1:7249/ds2man/basic/request&response/case5"
payload = {
"key1": "value1",
"key2": "value2"
}
async with aiohttp.ClientSession(
trust_env=True, timeout=aiohttp.ClientTimeout(total=120)
) as session:
async with session.post(url, json=payload) as response:
print(f"Headers:")
pprint(dict(response.headers))
data = await response.json()
print(f"\nJSON Body:")
print(f"type(data): {type(data)}")
pprint(data)
|
1
2
3
4
5
6
7
8
9
10
11
| Headers:
{'Content-Length': '46',
'Content-Type': 'application/json',
'Date': 'Tue, 06 May 2025 00:08:40 GMT',
'Server': 'uvicorn',
'x-echoed': 'True' # adding
}
JSON Body:
type(data): <class 'dict'>
{'received': {'key1': 'value1', 'key2': 'value2'}}
|
Summary
Feature | Request | Response |
---|
Origin | starlette.requests.Request | starlette.responses.Response |
Use case | Access request data from client | Customize what is sent to client |
Typical fields | .method , .headers , .body() | .headers , .set_cookie() , etc. |
Customizable | Yes (headers, body, method, etc.) | Yes (headers, cookies, status code) |