AI Hints
FastAPI · Lesson 11 of 12

Routers & Tests

⏱ 2 min read▶ Video at 23:43🎓 Free
Watch this lesson in the FastAPI courseWatch on YouTube ↗Full FastAPI course

Maya's API has grown. Menu items, orders, customers, staff logins. It all lives in one file of hundreds of lines. Finding anything takes forever, and a change in one place can quietly break something somewhere else. It is time to organize the code, and to prove that everything still works.

One file is not enough
From the video: One file is not enough
menu items
orders
customers
staff logins
Finding anything takes forever
A change here breaks something there

APIRouter: a mini application

FastAPI's answer to a big file is the API router. Think of it as a mini application. Each router owns one topic, with its own prefix, and its own tag. The menu router handles slash menu. The orders router handles slash orders.

APIRouter: a mini application
From the video: APIRouter: a mini application
Each router owns one topic: its own prefix, its own tag
menu routerprefix: /menu tag: menu
orders routerprefix: /orders tag: orders

A clean layout

Here is a clean project layout. The app folder holds main dot py, and a routers folder, with one file per topic. And next to it, a tests folder. Small files, with one clear job each.

terminal
find app tests -name '*.py' | sort
app/__init__.py
app/main.py
app/routers/__init__.py
app/routers/menu.py
app/routers/orders.py
tests/test_api.py
Small files, one clear job each

Create a router, then include it

The menu router creates an API router, with the prefix slash menu, and the tag menu. Its endpoints use plain router dot get, with paths relative to that prefix. In main dot py, we create the app, and include each router. That is all it takes.

app/routers/menu.pypython
router = APIRouter(prefix="/menu", tags=["menu"])

@router.get("/")
def list_items(): ...

@router.get("/{item_id}")
def read_item(item_id: int): ...
app/main.pypython
from app.routers import menu, orders

app = FastAPI(title="Sweet Crumbs API")

app.include_router(menu.router)
app.include_router(orders.router)
Paths are relative to the prefix
include_router: that is all it takes

The docs reflect that structure. Endpoints are grouped by tag, so menu and orders each get their own section.

Routers & Tests
From the video: Routers & Tests
Endpoints grouped by tag

Prove it with tests

Now, the proof. Automated tests. FastAPI ships a TestClient, built on httpx, that calls your app directly, without starting a server. You write plain functions that start with test. Make a request, and assert what you expect.

tests/test_api.pypython
client = TestClient(app)

def test_list_menu():
    response = client.get("/menu/")
    assert response.status_code == 200
Plain functions that start with test_

Four checks

We check that the menu returns three items. That a missing item gives a four hundred and four, with our message. That a valid order is created, with a two hundred and one. And that an invalid order is rejected, with a four twenty two.

test_list_menuthe menu has three items → 200
test_missing_item_returns_404missing item → 404 + our message
test_create_ordervalid order → 201 Created
test_invalid_order_is_rejectedinvalid order → 422

Run the tests

Run pytest dash v. Four tests, four passes, in a fraction of a second. If someone breaks the API later, a test turns red before a customer ever sees it.

terminal
python -m pytest -v
============================= test session starts ==============================
platform darwin -- Python 3.13, pytest 9
rootdir: ~/sweet-crumbs
collecting ... collected 4 items

tests/test_api.py::test_list_menu PASSED                                 [ 25%]
tests/test_api.py::test_missing_item_returns_404 PASSED                  [ 50%]
tests/test_api.py::test_create_order PASSED                              [ 75%]
tests/test_api.py::test_invalid_order_is_rejected PASSED                 [100%]

============================== 4 passed in 0.24s ===============================
4 passed in 0.24 s
Break the API later → a test turns red first

Summary

Maya's code is now organized, and tested. There is only one step left, and that is shipping it. In the final lesson, we deploy with Docker, and recap the whole course. See you there.

Key takeaways
  • APIRouter splits a big API into topics
  • include_router puts it together
  • TestClient + pytest prove it works
Next up: Deploy & Recap — ship it with Docker and look back