Skip to content

Routing & Sub-routers

JustAPI’s routing system uses a radix-trie (via matchit) for O(1) route matching. Routes can be registered with decorators, programmatically, or organized across multiple files using APIRouter.

from justapi import JustAPIApp
app = JustAPIApp()
@app.get("/users")
def list_users(request):
return [{"id": 1, "name": "Alice"}]
@app.post("/users")
def create_user(request):
return {"message": "User created"}

All standard HTTP methods are supported: @app.get(), @app.post(), @app.put(), @app.patch(), @app.delete(), @app.head(), @app.options(), @app.trace(), and @app.websocket().

Programmatic Route Registration (Non-Decorator)

Section titled “Programmatic Route Registration (Non-Decorator)”

Register routes without decorators using add_api_route():

def read_item(request, item_id: int):
return {"item_id": item_id, "name": "Item"}
app.add_api_route("/items/{item_id}", read_item, methods=["GET"])
app.add_api_websocket_route("/ws", my_ws_handler)

This is useful when importing handler functions from other modules or when routes need to be registered conditionally.

Group related routes in separate files:

app/routers/products.py
from justapi import APIRouter
router = APIRouter(prefix="/products", tags=["Products"])
@router.get("/")
def list_products(request):
return [{"id": 1, "name": "Rust Book"}]
@router.get("/{product_id}")
def get_product(request, product_id: int):
return {"product_id": product_id}
app/main.py
from justapi import JustAPIApp
from app.routers.products import router as products_router
app = JustAPIApp()
app.include_router(products_router, prefix="/api/v1")

APIRouter instances can include other APIRouter instances, creating nested route hierarchies:

from justapi import APIRouter
# Create a sub-router for user-related routes
users_router = APIRouter(prefix="/users")
@users_router.get("/{user_id}")
def get_user(request, user_id: int):
return {"user_id": user_id}
@users_router.get("/{user_id}/orders")
def get_user_orders(request, user_id: int):
return [{"order_id": 1, "user_id": user_id}]
# Create an admin router and include the users sub-router
admin_router = APIRouter(prefix="/admin", tags=["Admin"])
admin_router.include_router(users_router)
# Include the admin router in the app
app.include_router(admin_router, prefix="/api/v1")
# Final URL: /api/v1/admin/users/{user_id}

Use app.mount() to mount APIRouters or static directories:

# Mount an APIRouter as a sub-app
app.mount("/api/v1", users_router)
# Mount a static directory
app.mount("/static", "static", name="static")

Use the name parameter and url_for() to build URLs dynamically:

@app.get("/items/{item_id}", name="get_item")
def read_item(request, item_id: int):
...
# Build URL for a named route
url = app.url_for("get_item", item_id=42)
# Returns: "/items/42"

Named routes work with APIRouter too, and URLs are correctly resolved even with nested prefixes.

from justapi import APIRouter, Depends, HTTPException, Header
admin_router = APIRouter(prefix="/admin", tags=["Admin"])
def require_admin(authorization: str = Header(...)):
if authorization != "Bearer admin-token":
raise HTTPException(403, "Admin access required")
@admin_router.get("/users", dependencies=[Depends(require_admin)])
def list_all_users(request):
return [{"id": 1, "name": "Alice"}]
app.include_router(admin_router)

Routes are matched in order of registration. Specific routes before parameterized:

@app.get("/users/me") # Specific route first
def get_current_user(request):
return {"user": "current"}
@app.get("/users/{user_id}") # Parameterized route second
def get_user(request, user_id: int):
return {"user_id": user_id}

All registered routes are automatically documented:

URL Description
/docs Swagger UI
/redoc ReDoc
/scalar Scalar API Reference
/openapi.json Raw OpenAPI 3.1 spec