@app.page vs @app.get in Air: What’s the Difference?

Both decorators register GET routes in the feldroy/air framework, but @app.get requires you to explicitly define the URL path while @app.page automatically derives the path from the function name using compute_page_path.

The Air framework extends FastAPI to streamline HTML-first Python web development. When building applications, you choose between two decorators for handling GET requests, each serving distinct routing patterns and development workflows.

Path Definition: Explicit vs. Automatic

The fundamental distinction lies in how each decorator determines the URL endpoint.

@app.get Requires Explicit Path Configuration

When using @app.get, you must manually specify the complete URL pattern, including any path parameters. This decorator is implemented in src/air/routing.py within the RouterMixin.get method (lines 88-109) and provides full control over route configuration.

import air

app = air.Air()

@app.get("/users/{user_id}")
def user_profile(user_id: int) -> air.Div:
    return air.Div(f"Profile for user {user_id}")

This approach accepts all standard FastAPI path-operation arguments, including status_code, tags, dependencies, and response_model.

@app.page Uses Convention-Based Path Generation

The @app.page decorator eliminates manual path writing by calling compute_page_path from src/air/utils.py (lines 22-28). This utility transforms the function name into a URL automatically:

  • Underscores convert to dashes (default separator)
  • The special name index maps to the root path /

The implementation in src/air/routing.py (lines 313-351) computes the path and then delegates to self.get(), reusing the same registration logic while enforcing convention-over-configuration.

@app.page
def about_us() -> air.H1:
    # Automatically accessible at "/about-us"

    return air.H1("About Us")

@app.page
def contact_form() -> air.Div:
    # Automatically accessible at "/contact-form"

    return air.Div("Contact us")

Implementation Details in the Source Code

Both decorators ultimately register routes through the same underlying mechanism in src/air/routing.py, but their entry points differ:

  • RouterMixin.get calls _route("get", path, ...) directly to register the handler on the internal FastAPI router.
  • RouterMixin.page first invokes compute_page_path(func.__name__) to generate the path string, then calls self.get(page_path, ...) to complete registration.

Because page delegates to get, both decorators attach the same .url() helper method (added during _route processing) to the decorated function, enabling reverse URL lookup.

Practical Usage Examples

Here is a complete example demonstrating both decorators, including the special index case and URL generation:

import air

app = air.Air()

# Explicit API endpoint

@app.get("/api/status")
def health_check():
    return {"status": "ok"}

# Convention-based page routes

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

    return air.H1("Welcome Home")

@app.page
def user_settings() -> air.Div:
    # Maps to "/user-settings"

    return air.Div("Settings Page")

# URL reverse lookup works for both

print(index.url())           # Output: "/"

print(user_settings.url())   # Output: "/user-settings"

print(health_check.url())    # Output: "/api/status"

When to Use Each Decorator

Select the appropriate decorator based on your routing requirements:

  • Use @app.get when building API endpoints, requiring custom URL patterns with parameters, or needing to specify explicit FastAPI options like dependencies or response_class.
  • Use @app.page when creating HTML content pages where the URL should mirror the Python function name, reducing boilerplate and maintaining consistent naming conventions across your feldroy/air project.

Summary

  • Path derivation: @app.get requires manual path strings; @app.page auto-generates paths via compute_page_path in src/air/utils.py.
  • Implementation: Both use src/air/routing.py, with page delegating to get after computing the path.
  • Special handling: Function names like index automatically map to /, and underscores become URL separators.
  • Reverse URLs: Both decorators provide the .url() method for generating route URLs programmatically.
  • Flexibility: @app.get exposes all FastAPI kwargs directly; @app.page inherits them through delegation but locks the path to the convention.

Frequently Asked Questions

Can I customize the URL path when using @app.page?

No. The @app.page decorator strictly derives the path from the function name using compute_page_path in src/air/utils.py. If you need a custom URL pattern, use @app.get instead, which allows explicit path definitions like /custom/path/{id}.

Why does the index() function map to the root route "/"?

The compute_page_path utility specifically checks for the function name "index" and returns "/" as a special case. This convention allows you to define your homepage using a semantic function name while maintaining clean URL structure at the root path.

Do both decorators support FastAPI features like dependency injection?

Yes. Because RouterMixin.page in src/air/routing.py delegates to self.get() after computing the path, it inherits full compatibility with FastAPI path-operation kwargs. You can use dependencies, tags, status_code, and other standard arguments with both decorators.

How does the .url() method work on decorated functions?

During route registration in _route (called by both decorators), Air attaches a .url() helper to the function object. This method performs reverse URL lookup using the registered path, working identically for both @app.get and @app.page routes regardless of how the path was originally defined.

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 →