Request Bodies
Table of Contents
Why Request Bodies Matter
So far, you have seen how to send information through the URL, such as path parameters and query parameters. That works well for simple, small bits of data, for example a page number or an ID.
However, many backend tasks require sending more complex data, such as:
- A complete user profile with name, email, and password
- A blog post with title, content, tags, and publish date
- A list of items in a shopping cart
Putting all of that into the URL would be unreadable, insecure, and sometimes impossible. This is where request bodies come in.
A request body is the part of an HTTP request that contains data sent from the client to the server, outside of the URL and headers.
Typical methods that use bodies:
POSTto create somethingPUTorPATCHto update something- Sometimes
DELETEwith extra details - Rarely
GET(you should normally not send bodies with GET)
Important rule:
The request body is where you send the main data of an HTTP request, especially for POST, PUT, and PATCH. Path and query parameters should be used only for identification, filtering, or simple options.
Where the Body Fits in an HTTP Request
An HTTP request has three main parts:
- Request line: method + path + version
Example:
POST /users HTTP/1.1 - Headers: extra metadata about the request
Example:
Content-Type: application/json - Body: the actual data you send
Example:
{"name": "Alice", "email": "alice@example.com"}
In raw form, a request with a body might look like this:
POST /users HTTP/1.1
Host: example.com
Content-Type: application/json
Content-Length: 56
{"name":"Alice","email":"alice@example.com","age":30}Note that there is an empty line between headers and the body. Everything after that empty line is the body.
Request Bodies vs Query and Path Parameters
Use this mental model:
| Use Case | Prefer |
|---|---|
| Identifying a single resource | Path parameter |
| Filtering, sorting, pagination | Query parameters |
| Main data for create/update | Request body |
| File uploads | Request body |
| Small flags / options | Query parameters or body |
Examples:
- Get user with id 42
GET /users/42
Path parameter42is enough. No body needed.- List users on page 3, sorted by name
GET /users?page=3&sort=name
Only query parameters. No body.- Create a new user
POST /userswith body:
{"name": "Alice", "email": "alice@example.com"}
The body holds the actual user data.- Update a blog post
PUT /posts/10with body:
{"title": "New title", "content": "Updated content"}
Common Body Formats
The body is just bytes. To understand those bytes, the server must know what format they are in. This is what the Content-Type header is for.
Common formats:
| Content-Type | Typical Use |
|---|---|
application/json | JSON APIs, REST APIs |
application/x-www-form-urlencoded | HTML forms, simple form data |
multipart/form-data | File uploads with forms |
text/plain | Simple raw text |
application/xml | Older systems, some APIs |
For backend development today, you will mainly work with:
application/jsonfor API datamultipart/form-datafor file uploads
Other content types are used in special situations.
Important rule:
Always set the correct Content-Type header when sending a request body. The backend uses this header to decide how to parse the body.
JSON Request Bodies
JSON is the most common format for API request bodies.
Example: creating a new user
POST /users HTTP/1.1
Host: api.example.com
Content-Type: application/json
{
"name": "Alice",
"email": "alice@example.com",
"age": 30
}Here:
Content-Type: application/jsontells the server to parse the body as JSON.- The body is valid JSON: an object with keys and values.
From the backend perspective, in Python using FastAPI, you might define:
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class UserCreate(BaseModel):
name: str
email: str
age: int
@app.post("/users")
def create_user(user: UserCreate):
# 'user' contains data from the request body
return {"message": f"User {user.name} created"}The framework automatically:
- Reads the raw body bytes
- Parses them as JSON
- Validates the structure
- Converts it into a Python object
UserCreate
If the client sends invalid JSON, the framework will reject the request.
Nested JSON in Bodies
Bodies can contain nested structures:
{
"title": "My post",
"content": "Hello world",
"tags": ["intro", "hello"],
"author": {
"name": "Alice",
"id": 123
}
}The backend must be prepared to parse and validate these nested objects.
Form-Encoded Request Bodies
When you submit a classic HTML form without files, browsers usually use:
Content-Type: application/x-www-form-urlencoded
Example HTML:
<form action="/login" method="post">
<input type="text" name="username" />
<input type="password" name="password" />
<button type="submit">Login</button>
</form>The browser sends:
POST /login HTTP/1.1
Host: example.com
Content-Type: application/x-www-form-urlencoded
username=alice&password=secret123Notice:
- The body looks like query parameters.
- Key value pairs are separated by
&, and keys and values are URL encoded.
On the backend, frameworks provide helpers to read this as form data, not as JSON.
In FastAPI:
from fastapi import FastAPI, Form
app = FastAPI()
@app.post("/login")
def login(username: str = Form(...), password: str = Form(...)):
# username and password come from the request body (form-encoded)
return {"logged_in": True}Multipart Form Data (Files and Fields)
When you upload files with a form, the browser uses:
Content-Type: multipart/form-data
This format allows:
- Multiple fields
- Multiple files
- Mixed text and binary data
Example HTML:
<form action="/upload" method="post" enctype="multipart/form-data">
<input type="text" name="description" />
<input type="file" name="file" />
<button type="submit">Upload</button>
</form>The raw multipart body is quite complex, with boundaries, parts, and headers for each part. Frameworks handle this complexity for you.
In FastAPI:
from fastapi import FastAPI, File, UploadFile, Form
app = FastAPI()
@app.post("/upload")
def upload(
description: str = Form(...),
file: UploadFile = File(...)
):
# 'description' is text from the body
# 'file' is uploaded file data
return {"filename": file.filename, "description": description}Form data and file uploads are handled inside the request body, not in the URL.
Sending Request Bodies from Different Clients
Backend developers often need to test their endpoints. Here are examples using different tools.
Using curl
curl is a command-line tool to send HTTP requests.
- JSON body:
curl -X POST http://localhost:8000/users \
-H "Content-Type: application/json" \
-d '{"name": "Alice", "email": "alice@example.com"}'- Form body:
curl -X POST http://localhost:8000/login \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "username=alice&password=secret123"- Multipart with file upload:
curl -X POST http://localhost:8000/upload \
-F "description=Profile picture" \
-F "file=@/path/to/image.jpg"
Note that you usually do not set Content-Type manually for multipart with -F. curl sets it for you.
Using JavaScript fetch
From a browser or frontend app:
- JSON body:
fetch('/users', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
name: 'Alice',
email: 'alice@example.com'
})
});- Form body:
const params = new URLSearchParams();
params.append('username', 'alice');
params.append('password', 'secret123');
fetch('/login', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
body: params.toString()
});- Multipart with files:
const formData = new FormData();
formData.append('description', 'Profile picture');
formData.append('file', fileInput.files[0]);
fetch('/upload', {
method: 'POST',
body: formData // browser sets Content-Type automatically
});Combining Path, Query, and Body
You can receive data from all three sources at once.
Example endpoint: update a product
- Path: product id
- Query: preview mode flag
- Body: update data
Request example:
PATCH /products/123?preview=true HTTP/1.1
Host: api.example.com
Content-Type: application/json
{
"price": 19.99,
"name": "New Product Name"
}In FastAPI:
from fastapi import FastAPI, Path, Query
from pydantic import BaseModel
app = FastAPI()
class ProductUpdate(BaseModel):
price: float | None = None
name: str | None = None
@app.patch("/products/{product_id}")
def update_product(
product_id: int = Path(...),
preview: bool = Query(False),
update: ProductUpdate = ...
):
# product_id from path
# preview from query
# update from body
return {
"product_id": product_id,
"preview": preview,
"new_data": update.model_dump()
}Here each part has its own purpose:
- Path identifies which product
- Query describes how to process the request
- Body carries the new data values
Common Mistakes with Request Bodies
Beginners often run into similar issues.
Mistake 1: Missing or Wrong Content-Type
Example:
curl -X POST http://localhost:8000/users \
-d '{"name": "Alice"}'
If you forget -H "Content-Type: application/json", the server might:
- Try to parse it as form data
- Fail to parse and return a 400 response
Always set Content-Type to match the body format.
Mistake 2: Sending JSON but Not JSON.stringify in JavaScript
Wrong:
fetch('/users', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: { name: 'Alice', email: 'alice@example.com' } // not a string
});Right:
fetch('/users', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({ name: 'Alice', email: 'alice@example.com' })
});The body must be a string when you send JSON.
Mistake 3: Using GET with a Body
Many tools and servers ignore or mishandle bodies in GET requests.
Even though the HTTP specification does not completely forbid bodies with GET, you should avoid them.
Important rule:
Do not send request bodies with GET requests. Use query parameters for simple data, or switch to POST if you must send a body.
Mistake 4: Putting Sensitive Data in the URL
Passwords, tokens, and private information should never go in the URL, because:
- URLs are logged in many places
- URLs might show up in browser history
- Some systems cache URLs
Use the request body for sensitive data instead.
Bad:
GET /login?username=alice&password=secret123
Better:
POST /login HTTP/1.1
Content-Type: application/json
{"username": "alice", "password": "secret123"}Size and Limitations of Request Bodies
Request bodies can be large, for example when uploading files or sending big JSON documents. However, servers and proxies often have limits to:
- Prevent abuse
- Protect memory and CPU
Examples:
- A web server might reject bodies larger than 10 MB.
- An application might restrict single file uploads to 5 MB.
On the backend you can:
- Configure maximum body size
- Return a specific error when the body is too large
- Use streaming or chunked uploads for very large files
You will explore these topics later when learning about file handling and performance.
Summary
You now know:
- Request bodies carry the main data for create and update operations.
- The body format is defined by the
Content-Typeheader. - JSON is the most common body format in web APIs.
- Form and multipart bodies are used for HTML forms and file uploads.
- Path, query, and body each have their own specific roles.
- Common pitfalls include incorrect
Content-Type, invalid JSON, and putting sensitive data in URLs.
In later chapters, you will see how to integrate request bodies with validation, models, and full REST API design.
Views: 8
KAHIBARO