Question: I am learning FastAPI and understanding that its data validation using pydantic is one of its features. But after reading its put method example from its tutorial I have a question if I only want to let the put body contain the updated data(as the URL already has its id), how do I do that? Use the sample code from the tutorial as an example to what I mean, from fastapi import FastAPI from fastapi.encoders import jsonable_encoder from pydantic import BaseModel app = FastAPI() class Item(BaseModel): id: str description: str = "default description" price: Union[float, None] = None tax: float = 10.5 tags: list[str] = [] ... @app.put("/items/{item_id}") #async def update_item(item_id: str, item:Item): async def update_item(item_id: str, item): pass  If I code async def update_item(item_id: str, item:Item) then the body has to contain id property otherwise I will get 422 "field required". But I feel that is unnecessary because the URL /items/{item_id} already contains the id I just want body to contain the updated data. But when I coded async def update_item(item_id: str, item), to my surprise the item became the required QUERY PARAMETERS! As its document shows:  Why does it become query parameters then? This is my second question. I feel that is wrong because I prefer to query parameters for GET only. --- Update --- I guess the 2 methods Chris provided are the way FastAPI solves my first question (whether id should be one of Item's properties is another question, e.g. check What to do when REST POST provides an ID?), but I come from nodejs background so I would like to provide Nestjs solution in comparison. Using Nestjs sample code here https://docs.nestjs.com/controllers#full-resource-sample @Controller('cats') export class CatsController { @Post() create(@Body() createCatDto: CreateCatDto) { return 'This action adds a new cat'; } ... @Put(':id') update(@Param('id') id: string, @Body() updateCatDto: UpdateCatDto) { return `This action updates a #${id} cat`; }  As https://docs.nestjs.com/techniques/validation#mapped-types explains "The PartialType() function returns a type (class) with all the properties of the input type set to optional." export class CreateCatDto { name: string; age: number; breed: string; } export class UpdateCatDto extends PartialType(CreateCatDto) {} 

Answer: Solution 1 You could have a Pydantic model, which would act as the parent one and include every attribute that is shared between both POST and PUT request bodies. You could then create a submodel, containing only the id attribute and inheriting from the parent model (not the BaseModel), which would be used for POST requests. The example below is based on the one given in FastAPI's documentation. Some related answer can be found here. Working Example from fastapi import FastAPI from fastapi.encoders import jsonable_encoder from pydantic import BaseModel from typing import Union, List app = FastAPI() class Item(BaseModel): description: str = "default description" price: Union[float, None] = None tax: float = 10.5 tags: List[str] = [] class ItemCreate(Item): id: str items = { "foo": {"name": "Foo", "price": 50.2}, "bar": {"name": "Bar", "description": "The bartenders", "price": 62, "tax": 20.2}, "baz": {"name": "Baz", "description": None, "price": 50.2, "tax": 10.5, "tags": []}, } @app.get("/items/{item_id}", response_model=Union[Item,str]) async def read_item(item_id: str): if item_id in items: return items[item_id] else: return 'Item not found' @app.put("/items/{item_id}", response_model=Item) async def update_item(item_id: str, item: Item): update_item_encoded = jsonable_encoder(item) items[item_id] = update_item_encoded return update_item_encoded @app.post("/items") async def create_item(item: ItemCreate): new_item_encoded = jsonable_encoder(item) items[item.id] = new_item_encoded return 'Success'   Alternatively, as shown in this answer, you could use the same Item model for both PUT and POST requests, with an extra id parameter explicitly defined as Body in the create_item endpoint for POST requests. For instance: from fastapi import Body @app.post("/items") async def create_item(item: Item, id: str = Body(...)): new_item_encoded = jsonable_encoder(item) items[id] = new_item_encoded return 'Success'  In that case, the request body would look like this: { "item": { "description": "default description", "price": 0, "tax": 10.5, "tags": [] }, "id": "string" }  Solution 2 With regard to the recent update in your question, indicating that (1) item_id should not be left to the client to decide/pass it (which is what you asked for initally), but rather be auto-created on server side (by your db or some other mechanism), and (2) should have the ItemUpdate model inheriting from the Item model with all the fields being optional (as shown in the Nest.js example you provided), here's a working example on how to approach the problem in that way. The @optional decorator in utlis.py module below has been taken from here, which is an update of this original post, so that is compatible with Pydantic V2. Additional related posts around creating an optional/partial decorator can be found here, as well as here, here and here. There also seems to be a related package, namely pydantic-partial (haven't tested it though). Working Example main.py from fastapi import FastAPI, Body, HTTPException from fastapi.encoders import jsonable_encoder from pydantic import BaseModel from typing import Union, List from utils import optional import random import string app = FastAPI() items = { "foo": {"name": "Foo", "price": 50.2}, "bar": {"name": "Bar", "description": "The bartenders", "price": 62, "tax": 20.2}, "baz": {"name": "Baz", "description": None, "price": 50.2, "tax": 10.5, "tags": []}, } class Item(BaseModel): description: str = "default description" price: Union[float, None] = None tax: float = 10.5 tags: List[str] = [] @optional class ItemUpdate(Item): pass def check_item_id(item_id): if item_id not in items: raise HTTPException(status_code=400, detail='Item not found') @app.get("/items/{item_id}", response_model=Union[Item, str]) async def read_item(item_id: str): check_item_id(item_id) return items[item_id] @app.post("/items") async def create_item(item: Item): item_encoded = jsonable_encoder(item) item_id = ''.join(random.choices(string.ascii_letters + string.digits, k=5)) items[item_id] = item_encoded return f"Item '{item_id}' has been created" @app.put("/items/{item_id}", response_model=Union[Item, str]) async def update_item(item_id: str, item: ItemUpdate): check_item_id(item_id) old = items[item_id] new = jsonable_encoder(item) old.update((k,v) for k,v in new.items() if v is not None) return old  utils.py from pydantic import BaseModel from pydantic import create_model from typing import Optional import inspect def optional(*fields): def dec(cls): fields_dict = {} for field in fields: field_info = cls.__annotations__.get(field) if field_info is not None: fields_dict[field] = (Optional[field_info], None) OptionalModel = create_model(cls.__name__, **fields_dict) OptionalModel.__module__ = cls.__module__ return OptionalModel if fields and inspect.isclass(fields[0]) and issubclass(fields[0], BaseModel): cls = fields[0] fields = cls.__annotations__ return dec(cls) return dec  About Query Parameters As for the query parameters, the HTTP protocol does not prevent one from using query params when sending a request using PUT, POST, etc., methods. Hence, in FastAPI, when declaring other endpoint parameters that are not part of the path parameters, such as item_id path parameter in the example above, or explicitly declared otherwise, such as Item in the given example, they are automatically interpreted as query parameters of type str. That's the reason that item is expected as query parameter when removing the Item definition from it. Have a look at this answer and this answer for more details.