Get AI summaries of any video or article — Sign up free
Python FastAPI Tutorial (Part 8): Routers - Organizing Routes into Modules with APIRouter thumbnail

Python FastAPI Tutorial (Part 8): Routers - Organizing Routes into Modules with APIRouter

Corey Schafer·
5 min read

Based on Corey Schafer's video on YouTube. If you like this content, support the original creators by watching, liking and subscribing to their content.

TL;DR

APIRouter moves resource-specific API endpoints into separate modules while keeping the same external URLs and responses.

Briefing

FastAPI route organization gets a major upgrade by moving API endpoints out of a bloated main.py file and into dedicated modules using APIRouter. The payoff is practical: the external behavior stays identical—same URLs, same responses—while the internal codebase becomes easier to maintain, scale, and test as new features like forms and authentication are added.

The tutorial starts by pointing out how main.py can quickly become crowded: imports, lifespan setup, template routes for the front end, API routes for users and posts, and exception handlers all end up mixed together. Even when everything works, that structure becomes a maintenance risk once CRUD endpoints, async logic, and validation rules multiply. The fix is to split API routes into a routers/ package and register them back into the app.

An APIRouter is introduced as FastAPI’s mechanism for defining routes in separate files. Instead of decorating endpoints on the app object (app.get, app.post), endpoints are decorated on a router instance (router.get, router.post). When the router is included in main.py via app.include_router, a prefix can be applied to all routes in that module, and tags can be used to group endpoints in the automatic documentation.

Implementation follows a clear pattern. A routers directory is created, along with an empty __init__.py to make it a proper Python package. Two router modules are added: routers/users.py and routers/post.py. In each module, only the imports needed for that resource are brought in, reducing unnecessary dependencies in every file.

For route paths, the tutorial uses a key technique: inside each router module, the path strings are written relative to the router’s eventual prefix. For example, user endpoints that originally lived under /api/users are moved into users.py with paths starting as an empty string (''), so that when main.py includes the router with prefix /api/users, the final URL becomes correct. Other endpoints keep their relative segments like /{user_id}. The same approach is applied to posts, using a prefix of /api/posts and relative paths such as '' and /{post_id}.

After moving the endpoints, main.py is updated to import the routers and include them with app.include_router(users.router, prefix='/api/users', tags=['users']) and app.include_router(post.router, prefix='/api/posts', tags=['posts']). The tutorial also flags a common FastAPI gotcha: route function names can conflict with template route names, so endpoints should use unique, descriptive function names across modules.

Verification confirms the refactor didn’t change functionality. The homepage still loads, API endpoints still create users and posts correctly, and the Swagger docs become cleaner—users and posts appear as collapsible groups thanks to tags. With main.py reduced to under 150 lines, the structure now separates concerns cleanly: front-end views remain in main.py, while resource logic lives in routers. The tutorial closes by outlining future organization options (by resource, version, access level, or feature) and previews the next steps: front-end forms using fetch and later authentication rules.

Cornell Notes

APIRouter lets FastAPI developers split large main.py files into resource-focused modules without changing external behavior. Endpoints move from app.get/app.post to router.get/router.post inside routers/users.py and routers/post.py, then get reattached in main.py using app.include_router with a URL prefix like /api/users or /api/posts. Router paths are written relative to the prefix—often using '' for the base route—so the final URLs remain the same. Adding tags in include_router groups endpoints in the Swagger UI under collapsible “users” and “posts” sections. The result is cleaner maintenance, fewer merge conflicts on teams, and easier targeted testing and mocking of dependencies per router.

Why does splitting routes into APIRouter modules matter even when everything already works?

When all API endpoints live in main.py, the file accumulates imports, lifespan setup, template routes, API routes, and exception handlers. That makes it harder to find where a specific endpoint lives, increases the chance of merge conflicts, and complicates adding new features. Moving endpoints into routers/users.py and routers/post.py keeps front-end views in main.py while isolating API logic by resource, improving maintainability without altering URLs or responses.

What changes when moving an endpoint from main.py to a router file?

Two things change: the decorator and the path. app.get/app.post becomes router.get/router.post, and the path becomes relative to the router’s eventual prefix. For instance, a route originally under /api/users might be moved with a path of '' inside users.py, because main.py will include the router with prefix='/api/users'. The rest of the endpoint logic—async/await, database access, validation—stays the same.

How does using an empty string '' inside a router produce the correct base URL?

Router paths are relative. If main.py includes users.router with prefix='/api/users', then an endpoint defined in users.py with path='' effectively becomes '/api/users'. Adding a leading slash inside the router path would change how concatenation behaves, so the tutorial keeps the base route as '' to avoid unwanted trailing-slash behavior and potential 307 redirects.

How do tags improve FastAPI documentation after routers are added?

When including routers, main.py passes tags=['users'] or tags=['posts'] to app.include_router. Swagger UI then groups endpoints under labeled sections, making the docs easier to navigate. Without tags, endpoints would appear in a flat list, which becomes harder to scan as the API grows.

What potential naming conflict does FastAPI warn about in this setup?

FastAPI can use function names as route names by default. If a router defines a function with the same name as a template route (e.g., both named home), it can create conflicts. The tutorial’s mitigation is to use unique, descriptive function names for endpoints across routers and template routes.

What practical benefits come from the refactor beyond cleaner code layout?

The structure supports scalability and team workflows. Different developers can work on different router modules with fewer merge conflicts. It also enables more focused testing—routers can be tested individually and dependencies can be mocked per route—making it easier to validate behavior as new endpoints and features are introduced.

Review Questions

  1. When including a router with prefix='/api/users', what should the router’s base endpoint path be if the goal is to map to '/api/users' exactly?
  2. What two edits are required when moving an endpoint decorator from main.py to an APIRouter module?
  3. How do tags passed to app.include_router affect the Swagger UI layout?

Key Points

  1. 1

    APIRouter moves resource-specific API endpoints into separate modules while keeping the same external URLs and responses.

  2. 2

    Refactoring main.py into routers/users.py and routers/post.py reduces file clutter and makes endpoint discovery faster.

  3. 3

    Endpoints change from app.get/app.post to router.get/router.post, and their paths become relative to the router’s include_router prefix.

  4. 4

    Using '' as a router base path lets main.py’s prefix (e.g., /api/users) form the correct final route without relying on trailing slashes.

  5. 5

    app.include_router can attach tags to group endpoints in Swagger UI under collapsible sections like “users” and “posts”.

  6. 6

    Unique endpoint function names help avoid FastAPI route-name conflicts with template routes.

  7. 7

    The modular router structure supports scaling, team parallel work, and more targeted testing with mocked dependencies.

Highlights

The refactor keeps functionality identical—same URLs and responses—while relocating route registration from the app object to routers.
Router paths are written relative to the include_router prefix; using '' inside users.py turns into the full '/api/users' base route.
Swagger UI becomes cleaner immediately: tags passed during include_router group endpoints into “users” and “posts” sections.
main.py shrinks dramatically after removing API endpoints, leaving front-end template routes and configuration more readable.
Unique function names prevent route-name collisions between template routes and router endpoints.

Topics

  • FastAPI Routers
  • APIRouter
  • Route Organization
  • Swagger Tags
  • CRUD Endpoints

Mentioned