> ## Documentation Index
> Fetch the complete documentation index at: https://fastapi2day.codewithsiva.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Response Handling in FastAPI: Models, Codes & Errors

> Control outbound data with FastAPI response models, set HTTP status codes correctly, raise HTTP exceptions, and return custom response types.

Just as you validate and structure incoming requests, you must also control, sanitise, and format the data your API sends back. Returning raw internal data — with database IDs, hashed passwords, salary figures, or internal flags — is a common security mistake. FastAPI's `response_model` parameter gives you a clean, declarative way to filter exactly what leaves your server, while `HTTPException` lets you abort with precise error responses at any point. This lesson covers both, plus custom response classes for those times when JSON isn't what the client needs.

***

## Outbound Data Serialization Flow

When a route function returns data, FastAPI filters it against the response schema, strips out any fields not declared in that schema, serialises the allowed fields to JSON, and sets the configured HTTP status code.

```mermaid theme={null}
graph LR
    DbData[(Raw Internal Data)] --> Function[Route Function Returns Dict/Model]
    Function --> Filter{Response Model Filter}
    Filter -->|Allowed fields| Serializer[Pydantic Serialization]
    Filter -->|Excluded fields e.g. base_salary| Stripped[Removed from payload]

    Serializer --> Client[Client HTTP Response]
```

***

## Response Models

By declaring a `response_model` in your path operation decorator, you tell FastAPI to:

1. **Validate the output** — ensure the return data conforms to the schema.
2. **Serialize the data** — convert complex Python objects (like database models) into JSON.
3. **Filter private data** — exclude any fields not declared in the response model.

### Example: Sanitising Employee Data

Suppose your internal data contains private fields like `base_salary` and `tax_id` that you never want to expose to clients:

```python theme={null}
from fastapi import FastAPI, status
from pydantic import BaseModel

app = FastAPI()

# 1. Internal schema with sensitive fields
class EmployeeInternal(BaseModel):
    id: int
    name: str
    department: str
    base_salary: float
    tax_id: str

# 2. Public schema — only safe fields
class EmployeePublic(BaseModel):
    id: int
    name: str
    department: str

# Sample mock database
EMPLOYEES = {
    1: EmployeeInternal(id=1, name="Alice", department="Engineering", base_salary=8500.0, tax_id="TAX123"),
    2: EmployeeInternal(id=2, name="Bob", department="HR", base_salary=6000.0, tax_id="TAX456")
}

# 3. Use response_model to filter automatically
@app.get("/employees/{employee_id}", response_model=EmployeePublic)
def get_employee(employee_id: int):
    # Even though we return EmployeeInternal (with salary & tax_id),
    # FastAPI automatically strips those fields from the response!
    return EMPLOYEES.get(employee_id)
```

The client receives only:

```json theme={null}
{
    "id": 1,
    "name": "Alice",
    "department": "Engineering"
}
```

The `base_salary` and `tax_id` fields are never included in the response, regardless of what the function returns internally.

<Warning>
  Without a `response_model`, FastAPI returns everything your function returns. Always declare a `response_model` for endpoints that touch internal or sensitive data.
</Warning>

***

## Setting HTTP Status Codes

You can configure the default success status code for a route using the `status_code` parameter. Use the `status` module constants for readability and maintainability:

```python theme={null}
from fastapi import status

# Onboarding an employee should return 201 Created
@app.post(
    "/employees",
    response_model=EmployeePublic,
    status_code=status.HTTP_201_CREATED
)
def create_employee(employee: EmployeePublic):
    return employee
```

<Tip>
  Always use `status.HTTP_201_CREATED` instead of the raw integer `201`. It's self-documenting and IDE-friendly — your editor will auto-complete the constant name and catch typos.
</Tip>

***

## Raising HTTP Exceptions

When something goes wrong — a resource isn't found, the caller lacks permission, a business rule is violated — raise an `HTTPException` to halt execution immediately and return a clear error response:

```python theme={null}
from fastapi import HTTPException

@app.get("/employees/{employee_id}", response_model=EmployeePublic)
def get_employee(employee_id: int):
    employee = EMPLOYEES.get(employee_id)

    if not employee:
        # Halt execution and return a 404 to the client
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail=f"Employee with ID {employee_id} does not exist"
        )

    return employee
```

The client receives:

```json theme={null}
{
    "detail": "Employee with ID 999 does not exist"
}
```

### Common Exception Patterns

```python theme={null}
from fastapi import HTTPException, status

# 404 — Resource not found
raise HTTPException(
    status_code=status.HTTP_404_NOT_FOUND,
    detail="Employee not found"
)

# 400 — Bad request / business rule violation
raise HTTPException(
    status_code=status.HTTP_400_BAD_REQUEST,
    detail="Cannot transfer employee to the same department"
)

# 403 — Forbidden (authenticated but unauthorised)
raise HTTPException(
    status_code=status.HTTP_403_FORBIDDEN,
    detail="You do not have permission to modify this record"
)

# 409 — Conflict (duplicate resource)
raise HTTPException(
    status_code=status.HTTP_409_CONFLICT,
    detail="An employee with this email already exists"
)
```

***

## Response Model Options

FastAPI provides extra parameters on `response_model` to fine-tune the output:

```python theme={null}
# Exclude fields that are None (optional fields not set)
@app.get("/employees/{employee_id}", response_model=EmployeePublic, response_model_exclude_none=True)
def get_employee(employee_id: int):
    ...

# Exclude specific fields by name
@app.get("/employees/{employee_id}", response_model=EmployeePublic, response_model_exclude={"department"})
def get_employee(employee_id: int):
    ...

# Include only specific fields
@app.get("/employees/{employee_id}", response_model=EmployeePublic, response_model_include={"id", "name"})
def get_employee(employee_id: int):
    ...
```

***

## Custom Response Classes

Sometimes JSON isn't the right format — you might need to return HTML, plain text, a file download, or a redirect. FastAPI supports this with response classes:

```python theme={null}
from fastapi.responses import HTMLResponse, FileResponse, RedirectResponse

# Return an HTML page
@app.get("/welcome", response_class=HTMLResponse)
def welcome_page():
    return """
    <html>
        <body>
            <h1>Employee Onboarding Dashboard</h1>
            <p>Welcome to the company portal!</p>
        </body>
    </html>
    """

# Redirect to another URL
@app.get("/old-endpoint")
def redirect_old():
    return RedirectResponse(url="/new-endpoint", status_code=301)
```

<Note>
  For JSON APIs, you rarely need to return a custom response class directly — FastAPI handles JSON serialisation for you. Custom response classes are most useful for file downloads, HTML rendering, and redirects.
</Note>

***

## Complete Example: Create and Retrieve with Filtering

```python theme={null}
from fastapi import FastAPI, HTTPException, status
from pydantic import BaseModel, Field

app = FastAPI()

class EmployeeCreate(BaseModel):
    name: str = Field(..., min_length=2)
    department: str
    salary: float = Field(..., gt=0)

class EmployeeResponse(BaseModel):
    id: int
    name: str
    department: str
    # salary is intentionally excluded from the response

EMPLOYEES: dict[int, dict] = {}

@app.post("/employees", response_model=EmployeeResponse, status_code=status.HTTP_201_CREATED)
def create_employee(employee: EmployeeCreate):
    new_id = len(EMPLOYEES) + 1
    EMPLOYEES[new_id] = {"id": new_id, **employee.model_dump()}
    return EMPLOYEES[new_id]  # salary is stripped by response_model

@app.get("/employees/{employee_id}", response_model=EmployeeResponse)
def get_employee(employee_id: int):
    if employee_id not in EMPLOYEES:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail=f"Employee {employee_id} not found"
        )
    return EMPLOYEES[employee_id]
```
