# Middleware: Code That Runs on Every Request<no value>

## What will we learn?

- Which problem there is when you want to do something on **every** request.
- What **Middleware** is, and how it is related to the **Decorator** we already know.
- **`@app.middleware("http")`**, **`call_next`**, **`async`** and **`await`**.
- A **log** and **time measurement** for every request in the students API, and a **Header** that is added to every response.

## Why do we need this?

The students API from the previous lesson works. Now we want to know **what happens in it**: which request arrived, which code came back, and how long it took. The first way that comes to mind — add a log line to every Route:

```python
@app.get("/students")
def list_students():
    logging.info("GET /students")
    return students

@app.get("/students/{student_id}")
def get_student(student_id: int):
    logging.info("GET /students/" + str(student_id))
    ...
```

Three problems:

1. **The same line is copied** into every Route — five, ten, fifty times.
2. **A new Route where the line was forgotten** — is simply not logged.
3. **There is no way to know the response code like this** — 404 and 400 are created in `raise HTTPException`, after the log line, and 422 is created even before the Route runs. And also not how long the request took.

## Explanation from the basics

### The idea — Middleware

**Middleware** = code that **wraps every request** that reaches the server: it runs **before** the Route, passes the request on, and runs again **after** — when the response is already in hand.

```text
Client --request--> Middleware --------> Route (your function)
                    before: start the timer
Client <-response-- Middleware <-------- Route
                    after: status code, duration, log
```

### Like a Decorator — for the whole server

In the lesson on decorators we wrote a Decorator that measures time: it wraps **one function**, runs before and after it, and calls it in the middle. Middleware is exactly the same idea — only it wraps **all the requests to the server**, without touching any Route.

| | Decorator | Middleware |
|---|---|---|
| Wraps | One function | Every request to the server |
| The call in the middle | `func(*args, **kwargs)` | `await call_next(request)` |
| Where you add it | `@timed` above every function | Once, in `main.py` |

{{< box "analogy" "Analogy: the guard at the building's entrance" >}}
Everyone who enters the building passes the guard — no matter which floor they are going to. The guard records who came in and when, and who left and when. They do not need to stand next to every office. **Middleware = the guard at the entrance**, and the Routes = the offices.
{{< /box >}}

### Why do you need to wait correctly?

The Middleware passes the request to the Route, and then **waits** for the response. But a server receives many requests at once. If it "gets stuck" and waits for each request until the end, all the others stand in a queue.

{{< box "analogy" "Analogy: a waiter and a kitchen" >}}
A waiter passes an order to the kitchen. If they **stand next to the kitchen** until the dish is ready — all the other tables wait for them. A good waiter says "**let me know when the dish is ready**", and keeps taking orders from other tables. When the dish is ready — they come back, take it and serve it.
{{< /box >}}

### `async` and `await`

This is exactly what **`await`** is for. Inside the Middleware there is the line:

```python
response = await call_next(request)
```

- **`call_next(request)`** — passes the request on: to the matching Route (or to a 404 if there is none), and returns the response. This is **the order to the kitchen**.
- **`await`** — "**let me know when the dish is ready**": wait for the Route's response, and meanwhile the server is **free** to handle other requests. When the response is ready, the code continues from the next line.
- **`async def`** — `await` may be written **only** inside a function defined with `async def`. That is why the Middleware is defined like this.

{{< box "tip" "How much do you need to know about this in the course?" >}}
Only this pattern: `async def` at the top, and `await` before `call_next`. Our Routes stay a regular `def` — FastAPI handles them by itself. How the server manages the waiting behind the scenes (the event loop, `asyncio`) — we will learn later in the track.
{{< /box >}}

### `request` and `response`

| Object | What is in it (what we will use) |
|---|---|
| `request.method` | `"GET"`, `"POST"`... |
| `request.url.path` | The path, for example `"/students/2"` |
| `response.status_code` | The response code: `200`, `404`... |
| `response.headers` | The response's Headers — works like a dictionary |

## Syntax

```python
from fastapi import FastAPI, Request

app = FastAPI()

@app.middleware("http")
async def my_middleware(request: Request, call_next):
    # ... before the route
    response = await call_next(request)     # wait for the route's response
    # ... after the route
    return response                         # always pass the response on
```

- **`@app.middleware("http")`** — registers the function as Middleware for every HTTP request.
- **`request: Request`** — `Request` is imported from `fastapi`.
- **`return response`** — required. Without it the client does not get a response.

## First code example

**A log and a time measurement for every request in the students API.** At the top of `students_memory.py` (from the previous lesson) — we only add, no Route changes:

```python
import logging
import time

from fastapi import FastAPI, HTTPException, Request
from pydantic import BaseModel

logging.basicConfig(level=logging.INFO, format="%(levelname)s %(message)s")

app = FastAPI(title="Students API")

@app.middleware("http")
async def log_requests(request: Request, call_next):
    start = time.time()
    response = await call_next(request)
    ms = round((time.time() - start) * 1000, 1)
    logging.info(request.method + " " + request.url.path + " -> "
                 + str(response.status_code) + " (" + str(ms) + " ms)")
    return response

# ... students, StudentIn and all the routes stay exactly the same
```

Run it (`uvicorn students_memory:app --reload`), and send requests from Swagger (`/docs`). **The log in the Terminal:**

```text
INFO GET /students -> 200 (0.7 ms)
INFO GET /students/99 -> 404 (0.5 ms)
INFO POST /students -> 201 (1.3 ms)
INFO POST /students -> 400 (0.6 ms)
INFO GET /students/abc -> 422 (0.5 ms)
```

## Breaking down the code line by line

| Code | Explanation |
|---|---|
| `logging.basicConfig(...)` | Setting up the log, once (the logging lessons). |
| `@app.middleware("http")` | The next function will run on **every** request. |
| `async def log_requests(request: Request, call_next):` | The request, and `call_next` — the way to continue to the Route. |
| `start = time.time()` | **Before** the Route: start measuring. |
| `response = await call_next(request)` | Run the Route and wait for the response. |
| `ms = round((time.time() - start) * 1000, 1)` | **After**: how much time passed, in milliseconds. |
| `logging.info(...)` | One line for every request: the Method, the path, the code and the time. **Also 404, 400 and 422** — because the Middleware sees the final response, even when the Route did not run at all. |
| `return response` | Pass the response to the client. |

**Notice:** the times are very short — the data is in a list in memory. When we connect the server to a database, we will see the times go up — and a measurement like this is exactly the way to discover where the server "wastes" time.

## Another example

### A Header for every response

Middleware can also **change** the response. Here we add to every response a Header named `X-Process-Time`, with the time it took to handle it:

```python
@app.middleware("http")
async def add_process_time(request: Request, call_next):
    start = time.time()
    response = await call_next(request)
    ms = round((time.time() - start) * 1000, 1)
    response.headers["X-Process-Time"] = str(ms)
    return response
```

In Swagger, under **Response headers**, this now appears:

```text
x-process-time: 0.7
```

- **`response.headers[...] = ...`** — like adding a key to a dictionary.
- **`X-`** at the start of the name — a convention for a Header **of our own**, which is not part of the HTTP standard.
- You can have several Middlewares in the same server — every request goes through all of them.

### When not Middleware?

Middleware suits things that **every** request needs — a log, time measurement, Headers. A rule of **one Route** — for example "minimum age 16" in `POST /students` — belongs to the Route itself, and not to code that runs on the whole server.

## Common mistake

**Mistake 1: forgetting `return response`.**

```python
@app.middleware("http")
async def log_requests(request: Request, call_next):
    response = await call_next(request)
    logging.info(request.url.path)
```

The Middleware returns `None`, and the client gets a **500** — on **every** request, because Middleware runs on all of them.

**Mistake 2: forgetting `await`.**

```python
response = call_next(request)
```

Without `await`, you don't wait for the Route — and `response` is not a response. The server fails on every request. **`await` before `call_next`, always.**

**Mistake 3: `def` instead of `async def`.** Without `async`, you cannot use `await` inside the function: `SyntaxError: 'await' outside async function`.

**Mistake 4: Route logic inside Middleware.** `if request.url.path == "/students" and ...` — Middleware that checks specific paths is usually a sign that the code belongs to a Route.

<!-- exercises:start — generated by scripts/intro6/sync_site_exercises.py; edit scripts/intro6/exercises/ -->
## Practice

The exercises use only what was taught in this lesson and the lessons before it. Try on your own first, and only then open the solution.

### Exercise 1: What runs first?

```text
@app.middleware("http")
async def trace(request: Request, call_next):
    print("before", request.method, request.url.path)
    response = await call_next(request)
    print("after", response.status_code)
    return response


@app.get("/hello")
def hello():
    print("inside the route")
    return {"message": "hi"}
```

You send `GET /hello`. What will be printed, and in which order? And what will be printed for `GET /nothing`, a path that does not exist?

{{< solution >}}
```python
from fastapi import FastAPI, Request
from fastapi.testclient import TestClient

app = FastAPI()


@app.middleware("http")
async def trace(request: Request, call_next):
    print("before", request.method, request.url.path)
    response = await call_next(request)
    print("after", response.status_code)
    return response


@app.get("/hello")
def hello():
    print("inside the route")
    return {"message": "hi"}


client = TestClient(app)
client.get("/hello")
client.get("/nothing")
```

```text
before GET /hello
inside the route
after 200
before GET /nothing
after 404
```

- The Middleware runs **before** the Route, `await call_next(request)` runs the Route, and then we continue **after** — with the response in hand.
- A request to a path that does not exist also goes through the Middleware: there is no Route to run, and FastAPI returns 404 — and the Middleware sees that too.
{{< /solution >}}

### Exercise 2: A log for every request

Write a Middleware named `log_requests` that writes to the log, at the INFO level, one line for every request: the Method, the path and the response code — in the format of the expected output.

Use this small application: a list with one student, `GET /students` and `GET /students/{student_id}` (which returns 404 for a student that does not exist). Set up the log so that the lines are printed to the screen:

```py
logging.basicConfig(level=logging.INFO, format="%(levelname)s %(message)s", stream=sys.stdout)
logging.getLogger("httpx").setLevel(logging.WARNING)   # hide the test client's own log lines

students = [{"id": 1, "name": "Dana"}]
```

To test without running a server, send the three requests with FastAPI's `TestClient` — a tool that sends requests straight to the application from Python code:

```py
from fastapi.testclient import TestClient

client = TestClient(app)
client.get("/students")
client.get("/students/1")
client.get("/students/99")
```

**Expected output:**

```text
INFO GET /students -> 200
INFO GET /students/1 -> 200
INFO GET /students/99 -> 404
```

{{< solution >}}
```python
import logging
import sys

from fastapi import FastAPI, HTTPException, Request
from fastapi.testclient import TestClient

logging.basicConfig(level=logging.INFO, format="%(levelname)s %(message)s", stream=sys.stdout)
logging.getLogger("httpx").setLevel(logging.WARNING)   # hide the test client's own log lines

app = FastAPI()
students = [{"id": 1, "name": "Dana"}]


@app.middleware("http")
async def log_requests(request: Request, call_next):
    response = await call_next(request)
    logging.info(request.method + " " + request.url.path + " -> " + str(response.status_code))
    return response


@app.get("/students")
def list_students():
    return students


@app.get("/students/{student_id}")
def get_student(student_id: int):
    for student in students:
        if student["id"] == student_id:
            return student
    raise HTTPException(status_code=404, detail="student not found")


client = TestClient(app)
client.get("/students")
client.get("/students/1")
client.get("/students/99")
```

```text
INFO GET /students -> 200
INFO GET /students/1 -> 200
INFO GET /students/99 -> 404
```

(`stream=sys.stdout` and the `httpx` line are needed only to test with `TestClient`. On a real server, `basicConfig` with `level` and `format` is enough.)

One log line, in one place — and no Route changed. A Route added tomorrow will also get into the log automatically.
{{< /solution >}}

### Exercise 3: A Header with the handling time

Write a Middleware that measures how long it took to handle the request, and adds to the response a Header named `X-Process-Time` with the time in milliseconds. The application has one Route, `GET /` that returns `{"message": "hello"}`.

Check that the Header arrives — send a request with `TestClient` (a tool that sends requests straight to the application from Python code) and check the response's Headers:

```py
from fastapi.testclient import TestClient

client = TestClient(app)
response = client.get("/")
print(response.status_code)
print("Has X-Process-Time:", "x-process-time" in response.headers)
```

**Expected output:**

```text
200
Has X-Process-Time: True
```

{{< solution >}}
```python
import time

from fastapi import FastAPI, Request
from fastapi.testclient import TestClient

app = FastAPI()


@app.middleware("http")
async def add_process_time(request: Request, call_next):
    start = time.time()
    response = await call_next(request)
    ms = round((time.time() - start) * 1000, 1)
    response.headers["X-Process-Time"] = str(ms)
    return response


@app.get("/")
def home():
    return {"message": "hello"}


client = TestClient(app)
response = client.get("/")
print(response.status_code)
print("Has X-Process-Time:", "x-process-time" in response.headers)
```

```text
200
Has X-Process-Time: True
```

- `time.time()` before and after — exactly like in the Decorator that measures time.
- `response.headers` works like a dictionary. Headers do not depend on capital and small letters, so `x-process-time` finds `X-Process-Time`.
{{< /solution >}}

### Exercise 4: async and await

```text
@app.middleware("http")
async def log_requests(request: Request, call_next):
    start = time.time()
    response = await call_next(request)
    ms = round((time.time() - start) * 1000, 1)
    logging.info(request.url.path + " (" + str(ms) + " ms)")
    return response
```

1. What does the line `response = await call_next(request)` do? Explain with the analogy of the waiter and the kitchen.
2. What does the server do while `await` waits?
3. Why is the function defined with `async def` and not with `def`?
4. Do our Routes (`list_students`, `get_student`) also need to be `async def`?

{{< solution >}}
1. `call_next(request)` passes the request to the Route — that is **the order to the kitchen**. `await` is "**let me know when the dish is ready**": wait for the Route's response, and when it is ready the code continues to the next line — with the response in `response`.
2. **It handles other requests.** Like a waiter who does not stand next to the kitchen but keeps taking orders from other tables — the server does not "get stuck" on one request.
3. Because `await` **may be written only** inside a function defined with `async def`. With a regular `def`, Python will not allow the line at all: `SyntaxError: 'await' outside async function`.
4. **No.** The Routes stay a regular `def` — FastAPI handles them by itself. In this course `async` and `await` appear only in Middleware, in this pattern.
{{< /solution >}}

### Exercise 5: What is wrong?

```text
@app.middleware("http")
def log_requests(request: Request, call_next):
    response = call_next(request)
    logging.info(request.url.path)
```

Find three problems. And when is it **not** right to use Middleware?

{{< solution >}}
1. **`def` instead of `async def`** — Middleware in FastAPI must be `async def`.
2. **`await` is missing** before `call_next(request)` — without it you don't wait for the Route, and `response` is not the response.
3. **`return response` is missing** — the Middleware must return the response onwards. Without it the client gets nothing, and the server fails.

**When not:** Middleware suits things that **every** request needs — a log, time measurement, Headers. A business rule of one Route — for example "minimum age 16" in `POST /students` — belongs to the Route itself (or to a validation function), not to Middleware that runs on the whole server.
{{< /solution >}}

<!-- exercises:end -->

## Summary

- **Middleware** = code that wraps **every** request: before the Route, and after it. Like a Decorator — for the whole server.
- **`@app.middleware("http")`** + **`async def`** + **`await call_next(request)`** + **`return response`**.
- **Before** `call_next` — what is needed before the request (for example, starting to measure). **After** — there is a response in hand: the code, the Headers, the time.
- Uses: a **log** for every request, **time measurement**, **Headers** for every response.
- Rules of one Route — in the Route, not in Middleware.

<!-- docs:start — generated by scripts/intro6/site_docs.py; edit the links there -->

## Official documentation

- [FastAPI — Middleware](https://fastapi.tiangolo.com/tutorial/middleware/)
- [FastAPI — Concurrency and async / await](https://fastapi.tiangolo.com/async/)

<!-- docs:end -->
