2.9. HTTP Responses
Table of Contents
Understanding HTTP Responses
When your browser sends a request to a server, the server answers with an HTTP response. As a backend developer you will create these responses all the time, so you must understand what they contain and how they are structured.
This chapter focuses on the response side only. Other chapters explain HTTP methods, requests, headers, and status codes in more depth.
The Structure of an HTTP Response
An HTTP response has three main parts:
- Status line
- Headers
- Optional body (the content)
In raw text, a simple HTTP response looks like this:
HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
Content-Length: 42
<html><body><h1>Hello, world!</h1></body></html>Let us break that down:
HTTP/1.1 200 OKis the status line.Content-Type: ...andContent-Length: ...are headers.- The empty line separates headers from the body.
- The HTML after the empty line is the body.
A valid HTTP response must start with a status line, followed by headers, then an empty line, then the body (if any).
The Status Line
The status line is always the first line of the response. It has three parts:
- HTTP version
- Status code
- Reason phrase
Format:
<HTTP version> <status code> <reason phrase>Example:
HTTP/1.1 404 Not Found- HTTP version:
HTTP/1.1orHTTP/2etc. - Status code: a 3-digit number, like
200,404,500. - Reason phrase: short text that describes the status, like
OK,Not Found.
As a backend developer you usually set the status code in your framework, and the framework generates the status line.
Examples in pseudo Python with FastAPI-like code:
from fastapi import FastAPI, Response
app = FastAPI()
@app.get("/ok")
def ok():
return {"message": "Everything is fine"} # status defaults to 200
@app.get("/not-found")
def not_found():
return Response(content="Item not found", status_code=404)
@app.get("/server-error")
def server_error():
return Response(content="Something went wrong", status_code=500)These calls would produce status lines:
GET /ok→HTTP/1.1 200 OKGET /not-found→HTTP/1.1 404 Not FoundGET /server-error→HTTP/1.1 500 Internal Server Error
There is a separate chapter that goes deeply into status codes, so here you just need to remember:
- You always send exactly one status code.
- The status code tells the client whether the request succeeded or failed and how.
Response Headers
Headers provide extra information about the response. They are key-value pairs, one per line, before the empty line.
Examples of common response headers:
Content-Type: application/json; charset=utf-8
Content-Length: 349
Date: Mon, 03 Feb 2025 10:15:30 GMT
Server: example-server/1.0
Cache-Control: no-cache
Set-Cookie: session_id=abc123; HttpOnly; Path=/; SecureYou will see specific headers in later chapters (HTTP headers, cookies, caching, etc). Here is a quick summary of a few that matter for almost every response.
Content-Type
Content-Type tells the client what kind of content the body contains.
Common values:
| Type | Meaning | Example body |
|---|---|---|
text/html | HTML page | <html>...</html> |
text/plain | Plain text | Hello |
application/json | JSON data | {"id": 1, "name": "Alice"} |
image/png | PNG image | Binary image data |
application/pdf | PDF document | Binary PDF data |
text/css | CSS stylesheet | body { color: red; } |
application/javascript | JavaScript | console.log("Hi"); |
Always set the correct Content-Type header for your responses. Clients rely on it to handle the body correctly.
In code you usually do not set Content-Type manually for simple cases, the framework does it based on what you return.
Example:
from fastapi import FastAPI
from fastapi.responses import PlainTextResponse, HTMLResponse, JSONResponse
app = FastAPI()
@app.get("/text", response_class=PlainTextResponse)
def get_text():
return "Just plain text" # Content-Type: text/plain
@app.get("/html", response_class=HTMLResponse)
def get_html():
return "<h1>Hello</h1>" # Content-Type: text/html
@app.get("/json", response_class=JSONResponse)
def get_json():
return {"message": "Hi"} # Content-Type: application/jsonContent-Length
Content-Length is the size of the body in bytes.
Example:
Content-Length: 42Clients use this to know when the response body ends.
In most modern frameworks and web servers you do not compute this manually, the server calculates it.
Date
Date shows when the response was generated, in GMT.
Example:
Date: Mon, 03 Feb 2025 10:15:30 GMTYou rarely set this yourself, the web server adds it.
Server
Server identifies the software that generated the response, like:
Server: nginx/1.25.0Again, typically added by the web server, not by your application.
Set-Cookie
Set-Cookie instructs the client to store a cookie. Cookies and sessions have their own chapters, but you should recognize the header:
Set-Cookie: session_id=abc123; HttpOnly; Path=/; SecureAs a backend developer you will often set and read cookies.
The Response Body
The body is the content part of the response. It is optional:
- Some responses have no body, for example
204 No Content. - Redirects like
302 Foundoften have an empty or small body. - Most normal
200 OKresponses have a body with HTML, JSON, or another format.
Examples of bodies:
HTML body
HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
<html>
<body>
<h1>Welcome</h1>
<p>This is a simple page.</p>
</body>
</html>JSON body
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
{"id": 1, "name": "Alice", "is_active": true}Empty body
HTTP/1.1 204 No Content
Content-Length: 0When you build APIs, you will most often send JSON bodies.
Example in pseudo Python (FastAPI style):
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class User(BaseModel):
id: int
name: str
is_active: bool
@app.get("/user/{user_id}")
def get_user(user_id: int):
user = User(id=user_id, name="Alice", is_active=True)
return user # Automatically turned into JSON body
The framework converts the User object to JSON and puts it into the response body.
A Complete Example of an HTTP Response
Imagine a client sends:
GET /api/users/1 HTTP/1.1
Host: example.com
Accept: application/jsonA typical backend might respond:
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Content-Length: 83
Date: Mon, 03 Feb 2025 10:20:00 GMT
Server: example-api/1.0
{"id":1,"name":"Alice","email":"alice@example.com","is_active":true}Mapping that to your backend code:
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class User(BaseModel):
id: int
name: str
email: str
is_active: bool
@app.get("/api/users/{user_id}")
def get_user(user_id: int):
# In real code you would query a database
user = User(
id=user_id,
name="Alice",
email="alice@example.com",
is_active=True,
)
return userThe framework:
- Chooses
200 OKby default. - Serializes the
Userobject into JSON for the body. - Sets
Content-Type: application/json; charset=utf-8. - Computes
Content-Lengthfrom the body. - Adds
Dateand maybeServerheaders.
Different Types of HTTP Responses
HTTP responses can be grouped by the type of content and behavior they represent. You will use different types depending on whether you return a web page, an API response, or a file.
HTML Responses
Used for traditional web pages:
HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
<html>
<body>
<h1>Profile</h1>
<p>Name: Alice</p>
</body>
</html>The browser parses and renders the HTML.
JSON Responses
Used for APIs consumed by JavaScript, mobile apps, other services:
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
{"id": 1, "name": "Alice"}Other services parse the JSON, not humans.
Redirect Responses
Redirects tell the client to go to a different URL. Typical codes are 301, 302, 303, 307, 308.
Example:
HTTP/1.1 302 Found
Location: https://example.com/login
Content-Length: 0Browser behavior:
- Reads the
Locationheader. - Automatically sends a new request to that URL.
In code:
from fastapi import FastAPI
from fastapi.responses import RedirectResponse
app = FastAPI()
@app.get("/old-path")
def old_path():
return RedirectResponse(url="/new-path", status_code=302)File Download Responses
Used to send files like PDFs or images for download.
Example:
HTTP/1.1 200 OK
Content-Type: application/pdf
Content-Disposition: attachment; filename="report.pdf"
Content-Length: 12345
...binary PDF data...
The important header is Content-Disposition: attachment, which tells the browser to download instead of display the file inline.
In code (FastAPI style):
from fastapi import FastAPI
from fastapi.responses import FileResponse
app = FastAPI()
@app.get("/download-report")
def download_report():
return FileResponse("report.pdf", media_type="application/pdf", filename="report.pdf")How Backends Generate Responses
You almost never write full raw HTTP responses by hand. Instead, you:
- Decide what status code to send.
- Choose or build a response object in your framework.
- Set headers if needed.
- Return content that becomes the body.
Example pattern in pseudo Python:
from fastapi import FastAPI, Response
from fastapi.responses import JSONResponse, HTMLResponse
app = FastAPI()
@app.get("/simple")
def simple():
# Framework wraps this into:
# HTTP/1.1 200 OK
# Content-Type: application/json
return {"message": "OK"}
@app.get("/custom")
def custom():
# Manual control of status and headers
data = {"message": "Created"}
return JSONResponse(
content=data,
status_code=201,
headers={"X-My-Header": "my-value"},
)
@app.get("/html-page")
def html_page():
html = "<h1>Hello page</h1>"
return HTMLResponse(content=html, status_code=200)Conceptually, every response you return is turned into the three response parts:
- Status line with version and status code.
- Headers including at least
Content-Type, maybe others. - A body with your content, or no body.
Every HTTP response you send should be:
- A meaningful status code that matches what happened.
- A correct Content-Type that matches the body.
- Consistent body format across your API, usually JSON.
Summary
- An HTTP response is the server’s answer to a client’s HTTP request.
- It always starts with a status line: HTTP version, status code, reason phrase.
- It includes headers that describe the response, such as
Content-Type,Content-Length,Set-Cookie, and others. - It may include a body that holds the actual content, such as HTML, JSON, or file data.
- As a backend developer you rarely build raw responses. Instead, you use your framework to:
- Set the status code.
- Choose the response type (JSON, HTML, file, redirect).
- Optionally set headers.
- Provide the content that becomes the body.
Later chapters on HTTP status codes, headers, cookies, and responses in frameworks like FastAPI will show you how to use these pieces in real applications.
Views: 10
KAHIBARO