A response having a 422 status code (i.e., Unprocessable entity) will have a response body that specifies the error message, telling exactly which part of your request is missing or doesn’t match the expected format. The code snippet you povided shows that you are trying to post JSON data to an endpoint that is expecting user as a query parameter, rather than JSON payload. Hence, the 422 Unprocessable entity error.
Below are given various approaches on how to define a FastAPI endpoint that is expecting JSON data. Also, Python and JavaScript HTTP client examples are provided, in order to test the given backend endpoints.
Option 1
As per the documentation, when you need to send JSON data from a client (let's say, a browser) to your API, you send it as a request body (through a POST request). To declare a request body, you can use Pydantic models.
from fastapi import FastAPI
from pydantic import BaseModel
class Base(BaseModel):
user: str
app = FastAPI()
@app.post('/')
async def main(base: Base):
return base
Option 2
If one doesn't want to use Pydantic models, they could also use Body parameters directly defined in the endpoint. If only a single body parameter is defined (as in your example), you could use the special Body parameter embed. On a side note, the ellipsis (...) inside the Body type below is used to indicate that a value is required. To declare parameters as Optional instead, please have a look at this answer.
from fastapi import Body
@app.post('/')
async def main(user: str = Body(..., embed=True)):
return {'user': user}
Option 3
Another (less recommended) way would be to use a Dict type (or simply dict in Python 3.9+) to declare a key:value pair. However, in this way, you can't use custom validations for various attributes in your expected JSON, as you would do with Pydantic models or Body fields (e.g., check if an email address is valid, or if a string follows a specific pattern).
from typing import Dict, Any
@app.post('/')
async def main(payload: Dict[Any, Any]):
return payload
In the example above, payload could also be defined as payload: dict[Any, Any], or simply payload: dict.
Please note that the solution above would only consider a dictionary object valid, but not a list of dictionary objects. Thus, if one needs to post a list of dict objects, the endpoint should be defined as payload: List[dict] (or payload: list[dict]) and the input data should look like [{"user1": "foo"}] (for single dict object) or [{"user1": "foo"},{"user2": "bar"}] (for multiple dict objects).
Option 4
If you are confident that the incoming data is a valid JSON, you can use Starlette's Request object directly to get the request body parsed as JSON, using await request.json(). However, with this approach, not only can't you use custom validations for your attributes, but you would also need to define your endpoint with async def, since request.json() is an async method and thus, one needs to await it. There is, though, an alternative way to keep your endpoint defined with normal def, and have the async method called within an async def dependency (see this answer for more details). For more details on def vs async def, please have look at this answer.
from fastapi import Request
@app.post('/')
async def main(request: Request):
return await request.json()
If you wish, you could also implement some checking on the Content-Type request header value, before attempting to parse the data, similar to this answer. However, just because a request says application/json in the Content-Type header, it doesn't always mean that this is true, or that the incoming data is a valid JSON (e.g., it may be missing a curly bracket or have a key that does not have a value, and so on). Note that according to the latest RFC8259, JSON can take the form of any data type that is valid for inclusion inside JSON, not just arrays or objects. So, for instance, a single string or number would be valid JSON:
A JSON text is a serialized value. Note that certain previous
specifications of JSON constrained a JSON text to be an object or an
array. Implementations that generate only objects or arrays where a
JSON text is called for will be interoperable in the sense that all
implementations will accept these as conforming JSON texts.
Hence using this approach, you should be aware that the user could pass, for instance, a single number or string instead of dictionary or list of dictionaries and still be considered valid.
For the validation check, you could use a try-except block when you attempt to parse the data, allowing you to handle a possible JSONDecodeError. Again, if you need the endpoint defined with normal def instead, you could have the validation check below take place inside an async def dependency (see the linked answer earlier).
from fastapi import Request, HTTPException
from json import JSONDecodeError
@app.post('/')
async def main(request: Request):
content_type = request.headers.get('Content-Type')
if content_type is None:
raise HTTPException(status_code=400, detail='No Content-Type provided')
elif content_type == 'application/json':
try:
return await request.json()
except JSONDecodeError:
raise HTTPException(status_code=400, detail='Invalid JSON data')
else:
raise HTTPException(status_code=400, detail='Content-Type not supported')
If you would like the endpoint accepting both specific/pre-defined and arbitrary JSON data, please check this answer out. Also, note that FastAPI/Starlette uses the standard json library for parsing the data behind the scenes. If one is looking for a faster alternative, please have a look at this answer that demonstrates how to use orjson instead.
Test all the options above
Using Python requests library
Related answer can be found here.
import requests
url = 'http://127.0.0.1:8000/'
payload ={'user': 'foo'}
resp = requests.post(url=url, json=payload)
print(resp.json())
Related answers can be found here and here as well. For examples using axios, please have a look at this answer, as well as this answer and this answer.
fetch('/', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({'user': 'foo'})
})
.then(resp => resp.json()) // or, resp.text(), etc
.then(data => {
console.log(data); // handle response data
})
.catch(error => {
console.error(error);
});