How to Mount a FastAPI Sub-App Inside an Air Application

To mount a FastAPI sub-app inside an Air application, call the Air.mount() method with a URL path prefix and the sub-application instance, which proxies the call directly to FastAPI's native mounting system.

The feldroy/air repository provides an HTML-first framework built on top of FastAPI by composition, storing an internal FastAPI instance at self._app. Because Air exposes FastAPI's routing capabilities through direct proxy methods, you can integrate existing FastAPI sub-applications seamlessly without leaving the Air ecosystem.

How Air.mount() Proxies FastAPI

According to the feldroy/air source code, the Air.mount() method is implemented as a thin wrapper that forwards arguments to the underlying FastAPI instance. In src/air/applications.py (lines 18-25), the implementation delegates directly to self._app.mount():

def mount(self, path: str, app: Any, name: str | None = None) -> None:
    """Mount a sub-application."""
    self._app.mount(path, app, name=name)        # ← proxy to FastAPI

This means any ASGI-compatible application—including a standard FastAPI instance, a Starlette app, or even another Air application—can be mounted under a specific URL prefix. The proxy approach ensures full compatibility with FastAPI's mounting behavior while maintaining Air's HTML-first ergonomics.

Accessing the Underlying FastAPI Instance

For advanced use cases requiring direct FastAPI manipulation, Air exposes the fastapi_app property. As implemented in src/air/applications.py (lines 28-33), this property returns the internal FastAPI instance:

@property
def fastapi_app(self) -> FastAPI:
    """Access the underlying FastAPI app for advanced use cases."""
    return self._app

You can use this property to add custom OpenAPI endpoints, WebSocket routes, or other FastAPI-specific features that fall outside Air's standard API.

Practical Implementation Examples

The following examples demonstrate how to mount different types of sub-applications within an Air app.

Mounting a Standard FastAPI Sub-Application

Use this pattern when you need to integrate an existing FastAPI API alongside Air's HTML pages:

import air
from fastapi import FastAPI

# ---- Sub‑application -------------------------------------------------

api = FastAPI(title="User API")

@api.get("/users")
async def list_users():
    return [{"id": 1, "name": "Alice"}, {"id": 2, "name": "Bob"}]

# ---- Main Air application -------------------------------------------

app = air.Air()

# Mount the FastAPI sub‑app under the "/api" prefix

app.mount("/api", api)

# Add a regular Air page

@app.page
def index():
    return air.H1("Welcome to the Air site!")

Requests to http://localhost:8000/api/users are handled by the FastAPI api object, while the root (/) is served by Air's HTML-first route system.

Nesting Air Applications

Because Air.mount() delegates to FastAPI, you can mount one Air application inside another to create modular, nested architectures:

import air

# Sub‑Air application

sub_app = air.Air()

@sub_app.page
def sub_home():
    return air.H2("Sub‑app Home")

# Main Air application

main_app = air.Air()

# Mount the sub‑Air app under "/sub"

main_app.mount("/sub", sub_app)

@main_app.page
def home():
    return air.H1("Main Air App")

This configuration routes http://localhost:8000/sub/ to the sub_home page while maintaining completely separate rendering contexts.

Customizing FastAPI Directly via fastapi_app

When you need to customize OpenAPI generation or add routes that bypass Air's HTML-centric API, access the underlying FastAPI instance directly:

import air
from fastapi import FastAPI, HTTPException

# Create the Air app

app = air.Air()

# Add a custom OpenAPI endpoint via the underlying FastAPI instance

@app.fastapi_app.get("/openapi.json", include_in_schema=False)
async def custom_openapi():
    # Custom logic for OpenAPI generation

    raise HTTPException(status_code=403, detail="Forbidden")

This approach allows you to leverage FastAPI's full feature set without compromising Air's higher-level abstractions.

Summary

  • Air.mount() proxies directly to FastAPI's native mounting system at src/air/applications.py, lines 18-25.
  • Any ASGI-compatible application can be mounted under a URL prefix, including standard FastAPI apps and nested Air apps.
  • The fastapi_app property provides escape-hatch access to the underlying FastAPI instance for advanced customization.
  • Mounted sub-apps handle their own routes independently while coexisting with Air's HTML-first page system.

Frequently Asked Questions

Can I mount a standard FastAPI application inside Air?

Yes. The Air.mount() method accepts any ASGI-compatible application, including standard FastAPI instances. According to the implementation in src/air/applications.py, the method simply forwards the call to self._app.mount(), ensuring full compatibility with FastAPI's mounting behavior.

How do I access FastAPI-specific features like OpenAPI customization in Air?

Use the fastapi_app property exposed on any Air instance. This property returns the internal FastAPI object (lines 28-33 of src/air/applications.py), allowing you to add custom routes, modify OpenAPI schema generation, or configure WebSocket endpoints directly.

Can I nest Air applications inside each other?

Yes. Because Air implements the ASGI interface and its mount() method delegates to FastAPI, you can mount an Air sub-application inside a parent Air application using main_app.mount("/prefix", sub_app). This creates modular architectures where each Air app maintains its own routing and state context.

What URL path should I use when mounting a sub-application?

The path parameter in Air.mount() defines the URL prefix where the sub-application will be accessible. All routes in the mounted app will be prefixed with this path. For example, mounting an app with users at /api makes the users endpoint available at /api/users.

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 →