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

> Understand the difference between @app.page and @app.get in the feldroy/air framework. Learn how @app.page automatically sets routes from function names for efficient web development.

- Repository: [Feldroy/air](https://github.com/feldroy/air)
- Tags: deep-dive
- Published: 2026-03-01

---

**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`](https://github.com/feldroy/air/blob/main/src/air/routing.py) within the `RouterMixin.get` method (lines 88-109) and provides full control over route configuration.

```python
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`](https://github.com/feldroy/air/blob/main/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`](https://github.com/feldroy/air/blob/main/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.

```python
@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`](https://github.com/feldroy/air/blob/main/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:

```python
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`](https://github.com/feldroy/air/blob/main/src/air/utils.py).
- **Implementation**: Both use [`src/air/routing.py`](https://github.com/feldroy/air/blob/main/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`](https://github.com/feldroy/air/blob/main/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`](https://github.com/feldroy/air/blob/main/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.