Skip to main content

FastAPI APIRouter

As your FastAPI application grows, keeping all your routes in a single file becomes unwieldy and difficult to maintain. This is where APIRouter comes in - it's one of FastAPI's most powerful features that helps you organize your application into modular components.

What is APIRouter?

APIRouter is a class in FastAPI that allows you to create modular, reusable route handlers. Think of it as a "mini FastAPI application" that you can include in your main application. By using APIRouter, you can:

  • Organize your routes into separate files and directories
  • Group related endpoints together
  • Apply common configurations to a group of routes
  • Make your code more maintainable and easier to test

Basic Usage of APIRouter

Let's start with a simple example to see how APIRouter works:

python
from fastapi import APIRouter, FastAPI

app = FastAPI()

# Create a router instance
router = APIRouter()

# Define routes on the router
@router.get("/items/")
async def read_items():
return [{"name": "Item 1"}, {"name": "Item 2"}]

@router.get("/items/{item_id}")
async def read_item(item_id: int):
return {"item_id": item_id}

# Include the router in the main app
app.include_router(router)

Running this application and navigating to /items/ will return:

json
[
{
"name": "Item 1"
},
{
"name": "Item 2"
}
]

And navigating to /items/42 will return:

json
{
"item_id": 42
}

Organizing Routes with Prefixes

One of the key benefits of APIRouter is the ability to add a prefix to all routes in the router. This is especially useful when organizing your API by resource or feature:

python
from fastapi import APIRouter, FastAPI

app = FastAPI()

# Create a router with a prefix
router = APIRouter(prefix="/items")

# Now paths are relative to the prefix
@router.get("/") # This becomes /items/
async def read_items():
return [{"name": "Item 1"}, {"name": "Item 2"}]

@router.get("/{item_id}") # This becomes /items/{item_id}
async def read_item(item_id: int):
return {"item_id": item_id}

# Include the router in the main app
app.include_router(router)

Organizing Routes in Separate Files

To fully benefit from APIRouter, you should organize your routes in separate files. Here's a typical project structure:

my_app/
├── main.py
├── routers/
│ ├── __init__.py
│ ├── items.py
│ ├── users.py
│ └── orders.py

Let's see how this works:

routers/items.py:

python
from fastapi import APIRouter

router = APIRouter(
prefix="/items",
tags=["items"],
responses={404: {"description": "Not found"}},
)

@router.get("/")
async def read_items():
return [{"name": "Item 1"}, {"name": "Item 2"}]

@router.get("/{item_id}")
async def read_item(item_id: int):
return {"item_id": item_id}

routers/users.py:

python
from fastapi import APIRouter

router = APIRouter(
prefix="/users",
tags=["users"],
responses={404: {"description": "Not found"}},
)

@router.get("/")
async def read_users():
return [{"name": "John Doe"}, {"name": "Jane Smith"}]

@router.get("/{user_id}")
async def read_user(user_id: int):
return {"user_id": user_id}

main.py:

python
from fastapi import FastAPI
from routers import items, users

app = FastAPI(title="My API")

app.include_router(items.router)
app.include_router(users.router)

@app.get("/")
async def root():
return {"message": "Welcome to my API!"}

Advanced APIRouter Configuration

APIRouter provides several optional parameters to customize its behavior:

  • prefix: A string that will be prepended to all route paths
  • tags: A list of strings to be applied to all routes for API documentation grouping
  • dependencies: Dependencies that should be included in all routes
  • responses: Default responses for all routes
  • callbacks: Default callbacks for all routes

Here's how to use some of these advanced features:

python
from fastapi import APIRouter, Depends, HTTPException, Security
from fastapi.security import OAuth2PasswordBearer

oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")

def get_token_header(token: str = Security(oauth2_scheme)):
if token != "fake-super-secret-token":
raise HTTPException(status_code=400, detail="Invalid token")
return token

router = APIRouter(
prefix="/items",
tags=["items"],
dependencies=[Depends(get_token_header)], # Applied to all routes
responses={404: {"description": "Not found"}},
)

@router.get("/")
async def read_items():
# This endpoint requires a valid token due to the router dependencies
return [{"name": "Item 1"}, {"name": "Item 2"}]

Nested Routers

You can even include routers within other routers to create deeply nested route structures:

python
from fastapi import APIRouter, FastAPI

app = FastAPI()

parent_router = APIRouter(prefix="/parent")
child_router = APIRouter(prefix="/child")

@child_router.get("/items")
async def read_child_items():
return [{"name": "Child Item 1"}, {"name": "Child Item 2"}]

# Include child router in parent router
parent_router.include_router(child_router)

# Include parent router in app
app.include_router(parent_router)

# This creates a path: /parent/child/items

Real-World Example: Building a Blog API

Let's create a more comprehensive example showing how APIRouter can be used in a real-world scenario. We'll build a simplified blog API with routes for posts, comments, and users.

routers/posts.py:

python
from fastapi import APIRouter, HTTPException
from pydantic import BaseModel
from typing import List, Optional

router = APIRouter(prefix="/posts", tags=["posts"])

class Post(BaseModel):
id: Optional[int] = None
title: str
content: str
author_id: int

# Dummy data
posts_db = [
Post(id=1, title="First Post", content="Hello world!", author_id=1),
Post(id=2, title="FastAPI Rocks", content="FastAPI is amazing!", author_id=1)
]

@router.get("/", response_model=List[Post])
async def get_posts():
return posts_db

@router.get("/{post_id}", response_model=Post)
async def get_post(post_id: int):
for post in posts_db:
if post.id == post_id:
return post
raise HTTPException(status_code=404, detail="Post not found")

@router.post("/", response_model=Post, status_code=201)
async def create_post(post: Post):
post.id = max(p.id for p in posts_db) + 1 if posts_db else 1
posts_db.append(post)
return post

routers/users.py:

python
from fastapi import APIRouter, HTTPException
from pydantic import BaseModel
from typing import List, Optional

router = APIRouter(prefix="/users", tags=["users"])

class User(BaseModel):
id: Optional[int] = None
username: str
email: str

# Dummy data
users_db = [
User(id=1, username="johndoe", email="[email protected]"),
User(id=2, username="janedoe", email="[email protected]")
]

@router.get("/", response_model=List[User])
async def get_users():
return users_db

@router.get("/{user_id}", response_model=User)
async def get_user(user_id: int):
for user in users_db:
if user.id == user_id:
return user
raise HTTPException(status_code=404, detail="User not found")

main.py:

python
from fastapi import FastAPI
from routers import posts, users

app = FastAPI(
title="Blog API",
description="A simple blog API built with FastAPI",
version="0.1.0"
)

app.include_router(posts.router)
app.include_router(users.router)

@app.get("/")
async def root():
return {
"message": "Welcome to the Blog API",
"documentation": "/docs"
}

APIRouter with Dependency Injection

You can also use APIRouter with FastAPI's powerful dependency injection system to share functionality across routes:

python
from fastapi import APIRouter, Depends, HTTPException
from typing import Optional

def common_parameters(q: Optional[str] = None, skip: int = 0, limit: int = 100):
return {"q": q, "skip": skip, "limit": limit}

router = APIRouter(prefix="/items", tags=["items"])

@router.get("/")
async def read_items(commons: dict = Depends(common_parameters)):
# You can access commons["q"], commons["skip"], commons["limit"]
return {
"items": [{"item_id": "Foo"}, {"item_id": "Bar"}],
"query_params": commons
}

@router.get("/search")
async def search_items(commons: dict = Depends(common_parameters)):
# Same dependency used in multiple routes
return {
"items": [{"item_id": "Foo"}, {"item_id": "Bar"}],
"query_params": commons
}

Summary

APIRouter is a powerful tool in FastAPI that helps you:

  1. Organize your code into modular, maintainable components
  2. Group related endpoints with common prefixes, tags, and dependencies
  3. Split your API logic into separate files for better code organization
  4. Apply common configurations to groups of routes
  5. Create nested route structures for complex APIs

By leveraging APIRouter effectively, you can build FastAPI applications that scale well as they grow in complexity, keeping your codebase organized and maintainable.

Additional Resources

Exercises

  1. Create a simple library management API with routers for books, authors, and borrowers.
  2. Build an e-commerce API with routers for products, orders, and customers with authentication requirements.
  3. Extend the blog API example with a comments router that includes routes for creating, reading, updating, and deleting comments.
  4. Create a nested router structure for a social media API with routes for users, posts, comments, and likes.
  5. Implement dependency injection for authentication across different routers in any of the above examples.


If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)