Post

Request and Response

Let's learn request and response.

Request and Response

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

AttributeDescription
request.methodHTTP method used for the request
(e.g., GET, POST, PUT, DELETE, OPTIONS)
request.urlFull URL of the incoming request
(e.g., http://localhost:9248/ds2man/basic/task/?question=Hello&user_name=DS2Man)
request.base_urlBase URL excluding the path and query string
(e.g., http://localhost:9248/)
request.headersAll HTTP headers (case-insensitive multi-dict)
request.query_paramsQuery parameters parsed as a dictionary-like object
(e.g., question=Hello&user_name=DS2Man)
request.path_paramsPath parameters extracted from the route
(e.g., /items/{item_id}{"item_id": 123})
request.cookiesCookies included in the request, as a dictionary
request.clientClient’s IP address and port, e.g.,
(e.g., Address(host=’127.0.0.1’, port=10519)
request.scopeLow-level ASGI connection information (type, headers, path, etc.)
- used internally by Starlette
request.statePer-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

FeatureRequestResponse
Originstarlette.requests.Requeststarlette.responses.Response
Use caseAccess request data from clientCustomize what is sent to client
Typical fields.method, .headers, .body().headers, .set_cookie(), etc.
CustomizableYes (headers, body, method, etc.)Yes (headers, cookies, status code)
This post is licensed under CC BY 4.0 by the author.