How to Organize Routes in Multiple Files Using AirRouter

Use air.AirRouter() to define path operations in separate modules, then register them with your main application via app.include_router() to keep your codebase modular and maintainable.

Air is a lightweight wrapper around FastAPI that provides the AirRouter class specifically for organizing routes across multiple files. By leveraging this pattern, you can group related endpoints into dedicated modules—such as users.py or blog.py—while maintaining full compatibility with FastAPI's routing system as implemented in src/air/routing.py. This approach keeps your main application file clean and makes large projects easier to navigate and test.

Understanding the AirRouter Pattern

AirRouter inherits from FastAPI's APIRouter and is defined in src/air/routing.py【/cache/repos/github.com/feldroy/air/main/src/air/routing.py#L395-L433】. It allows you to create isolated groups of routes that can be configured with shared prefixes, tags, and dependencies before being attached to the main Air application.

The typical workflow involves four steps:

  1. Instantiate air.AirRouter() with optional configuration (prefix, tags, etc.)
  2. Define routes using decorators like @router.get() or @router.page()
  3. Export the router from the module
  4. Import and register the router using app.include_router()

Creating Modular Route Files

Define a User Router with Prefixes

Create a dedicated file for user-related endpoints and configure a URL prefix at the router level:


# users.py

import air

router = air.AirRouter(prefix="/users", tags=["users"])

@router.get("/")
def list_users() -> air.H1:
    """GET /users – list all users."""
    return air.H1("User list")

@router.get("/{user_id}")
def get_user(user_id: int) -> air.H1:
    """GET /users/{user_id} – show a single user."""
    return air.H1(f"User {user_id}")

@router.post("/")
def create_user() -> air.H1:
    """POST /users – create a new user."""
    return air.H1("User created")

__all__ = ["router"]

The prefix="/users" argument ensures all routes in this module automatically mount under /users, while tags=["users"] groups them in the auto-generated OpenAPI documentation.

Configure Automatic Page Routes

For HTML page routes that derive their URL path from function names, use the @router.page decorator with a custom path_separator:


# blog.py

import air

router = air.AirRouter(
    prefix="/blog", 
    tags=["blog"], 
    path_separator="-"
)

@router.page  # automatic route → "/blog"

def index() -> air.H1:
    return air.H1("Blog front page")

@router.page  # function name becomes "/blog/recent-posts"

def recent_posts() -> air.H1:
    return air.H1("Recent posts")

The path_separator parameter (handled in AirRouter.__init__ at lines 333-340 of src/air/routing.py) controls how function names are transformed into URL paths when using the automatic page routing feature【/cache/repos/github.com/feldroy/air/main/src/air/routing.py#L333-L340】.

Wiring Routers to the Main Application

Import your modular routers into the main application file and attach them using include_router(). This method is forwarded to the underlying FastAPI application in src/air/applications.py:


# app.py

import air
from users import router as users_router
from blog import router as blog_router

app = air.Air()

# Attach the modular routers

app.include_router(users_router)   # mounts under /users

app.include_router(blog_router)    # mounts under /blog

# Define routes directly on the app if needed

@app.page
def home() -> air.H1:
    return air.H1("Welcome to the Air demo")

Running this application with uvicorn app:app exposes the following routes:

  • / (GET) → home
  • /users/ (GET) → list_users
  • /users/{user_id} (GET) → get_user
  • /users/ (POST) → create_user
  • /blog (GET) → index
  • /blog/recent-posts (GET) → recent_posts

Repository Examples and Testing

The examples/src/routing__RouterMixin__page.py file provides a minimal working demonstration of this pattern【/cache/repos/github.com/feldroy/air/main/examples/src/routing__RouterMixin__page.py#L1-L22】:

import air

app = air.Air()
router = air.AirRouter()

@app.page
def index() -> air.H1:  # → "/"

    return air.H1("I am the home page")

@router.page
def data() -> air.H1:    # → "/data"

    return air.H1("I am the data page")

@router.page
def about_us() -> air.H1:  # → "/about-us"

    return air.H1("I am the about page")

app.include_router(router)

For comprehensive usage patterns—including prefix handling, custom separators, and dependency injection—refer to the test suite in tests/test_routing.py, which validates the full router lifecycle from definition to inclusion.

Summary

  • Create routers in separate files using router = air.AirRouter() with optional prefix and tags parameters
  • Define routes using standard decorators (@router.get(), @router.post(), @router.page()) that mirror FastAPI's API
  • Export and import the router instances into your main application file to keep the entry point clean
  • Register with include_router() to mount the routes at their configured prefixes
  • Customize behavior via path_separator for automatic page routes or shared dependencies for middleware-like functionality

All routing logic is implemented in src/air/routing.py, with the main application integration handled in src/air/applications.py, ensuring full FastAPI compatibility while simplifying multi-file organization.

Frequently Asked Questions

Does AirRouter support all FastAPI APIRouter features?

Yes. Because AirRouter inherits directly from FastAPI's APIRouter class, it supports all standard features including route-specific dependencies, response models, exception handlers, and custom route classes. You can use dependencies, responses, and default_response_class parameters exactly as you would in standard FastAPI applications.

How do I change the URL path separator for automatic page routes?

Pass the path_separator argument when creating the router: air.AirRouter(path_separator="-"). This converts function names like about_us into URL paths like /about-us instead of the default /about_us. This configuration is processed in the AirRouter.__init__ method around line 333 of src/air/routing.py.

Can I nest AirRouters within other AirRouters?

While FastAPI supports nested routers, the Air framework typically expects you to register all routers directly with the main Air application via app.include_router(). This flat structure keeps the routing table explicit and easier to debug, though you can technically include a router in another router before attaching it to the app if you need hierarchical organization.

Where is the AirRouter class defined in the source code?

The AirRouter class is defined in src/air/routing.py between lines 395 and 433【/cache/repos/github.com/feldroy/air/main/src/air/routing.py#L395-L433】, alongside the AirRoute class and the page helper method. This file also contains the RouterMixin that provides the include_router() method used by the main Air application class.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →