Middleware
Pyxle supports two levels of middleware: application-level middleware that wraps every request, and route-level hooks that run only for specific route types.
Application-level middleware
Add Starlette-compatible middleware classes to your config:
{
"middleware": [
"myapp.middleware:LoggingMiddleware",
"myapp.middleware:TimingMiddleware"
]
}Each entry is a string in module.path:ClassName format. The class must be a standard Starlette middleware or BaseHTTPMiddleware subclass.
Writing a middleware
# myapp/middleware.py
from starlette.middleware.base import BaseHTTPMiddleware
from starlette.requests import Request
from starlette.responses import Response
class TimingMiddleware(BaseHTTPMiddleware):
async def dispatch(self, request: Request, call_next):
import time
start = time.perf_counter()
response = await call_next(request)
duration = time.perf_counter() - start
response.headers["X-Response-Time"] = f"{duration:.3f}s"
return responseMiddleware is applied in the order listed in config. The first middleware in the list is the outermost wrapper.
Route-level hooks
Route hooks run before and after specific route handlers. Configure them per route type:
{
"routeMiddleware": {
"pages": ["myapp.hooks:require_auth"],
"apis": ["myapp.hooks:rate_limit"]
}
}Writing a route hook (function style)
# myapp/hooks.py
from starlette.requests import Request
from starlette.responses import Response, JSONResponse
async def require_auth(context, request: Request, call_next):
token = request.cookies.get("session")
if not token:
return JSONResponse({"error": "Unauthorized"}, status_code=401)
return await call_next(request)The function receives three arguments:
context-- aRouteContextwith metadata about the matched routerequest-- the StarletteRequestcall_next-- an async callable that invokes the next handler
Writing a route hook (class style)
from pyxle.devserver.route_hooks import RouteHook
class AuditHook(RouteHook):
async def on_pre_call(self, request, context):
# Runs before the handler
print(f"Request to {context.path}")
async def on_post_call(self, request, response, context):
# Runs after the handler
print(f"Response status: {response.status_code}")
async def on_error(self, request, context, exc):
# Runs when the handler raises
print(f"Error: {exc}")RouteContext
The context object provides metadata about the matched route:
| Property | Type | Description |
|---|---|---|
target |
"page" | "api" |
Route type |
path |
str |
URL path pattern |
source_relative_path |
Path |
File path relative to project root |
module_key |
str |
Python import key |
has_loader |
bool |
Whether the page has a @server loader |
allowed_methods |
tuple[str, ...] |
HTTP methods the route handles |
Built-in hooks
Pyxle applies two default hooks:
attach_route_metadata-- adds route info torequest.scope["pyxle"]["route"]enforce_allowed_methods-- returns 405 for disallowed API methods
Middleware execution order
From outermost to innermost:
- Custom middleware (from
middlewareconfig) - CORS middleware (if configured)
- CSRF middleware (if enabled)
- Vite proxy (dev mode)
- Static file serving
- Route hooks (from
routeMiddlewareconfig) - Page/API handler
Next steps
- Configure environment variables: Environment Variables
- Add CORS and CSRF: Security