On this page
API reference for fastware middleware: CORS headers, request ID tracing, request timing, trusted host validation, and Vite dev proxy routing.
#Middleware API Reference
All middleware classes are pure ASGI -- no framework dependency beyond fastware's own send_error helper. This makes them streaming-safe (SSE, WebSocket) and avoids the response-buffering issues of BaseHTTPMiddleware-style wrappers.
Built-in middleware is automatically applied by create_app when the corresponding AppConfig fields are set. You can also use these classes directly for custom middleware stacks.
#src.fastware.middleware
Pure ASGI middleware for request tracing, CORS headers, trusted-host validation, and Vite dev proxy routing, all streaming-safe for SSE and WebSocket.
All middleware classes are pure ASGI -- no framework dependency beyond fastware's own send_error helper. This keeps them streaming-safe (SSE, WebSocket) and avoids the response-buffering issues of BaseHTTPMiddleware-style wrappers.
Classes: RequestIDMiddleware — assigns/propagates X-Request-Id per request RequestTimingMiddleware — logs method, path, status, duration; ring buffer CORSMiddleware — preflight OPTIONS + response header injection TrustedHostMiddleware — rejects requests from unlisted Host headers ViteDevProxy — proxies non-API requests to a Vite dev server
#RequestIDMiddleware
Assign or propagate a unique request ID per request.
If the incoming request carries an X-Request-Id header, that value is reused. Otherwise a new UUID4 is generated. The ID is stored in scope["state"]["request_id"] and returned as an X-Request-Id response header.
When structlog is available, contextvars are cleared at the start of each request (preventing context leak from a previous request) and the request ID is bound so all log entries within the request include it.
#RequestTimingMiddleware
Log every HTTP request with method, path, status, and duration.
Wraps send to capture the status code from http.response.start, then uses try/finally so timing fires even for long-lived SSE streams (when the client disconnects the ASGI handler returns).
Args:
app: The inner ASGI application.error_log: OptionalErrorLoginstance. On 5xx responses the
middleware calls error_log.append(...) to persist the failure for dashboard visibility.
exclude_paths: Iterable of path prefixes to exclude from the ring
buffer (e.g. ["/events"]). Excluded requests are still logged, just not stored.
maxlen: Maximum number of entries in the ring buffer (default 10000).
#CORSMiddleware
Add CORS headers to responses and handle preflight OPTIONS requests.
Args:
app: The inner ASGI application.allow_origins: List of allowed origins (e.g.["http://localhost:5173"]).
Use ["*"] to allow any origin.
allow_methods: HTTP methods to advertise. Defaults to common methods.allow_headers: Request headers the client may send. Defaults to
common headers.
allow_credentials: Whether to setAccess-Control-Allow-Credentials.
#_cors_headers
def _cors_headers(self, origin: str) -> list[tuple[bytes, bytes]]Build the list of CORS response headers for origin.
#TrustedHostMiddleware
Reject requests whose Host header is not in the allow-list.
Prevents DNS rebinding attacks for servers bound to localhost.
Args:
app: The inner ASGI application.allowed_hosts: List of hostnames (with optional port) to allow.
Use ["*"] to disable the check.
#ViteDevProxy
ASGI middleware that proxies unmatched requests to a Vite dev server.
Uses a backend-first routing strategy for HTTP: every request hits the backend first. If the backend returns 404 (no route matched), the request is proxied to Vite instead. This means backend routes like /health or /metrics work without being under an API prefix.
WebSocket upgrades cannot be retried, so they still use prefix-based routing: paths matching api_prefix or /events go to the backend; everything else is proxied to Vite (for HMR).
Args:
app: The inner ASGI application.vite_port: Port the Vite dev server is listening on.api_prefix: Path prefix for backend WebSocket routes (default
"/api"). Only used for WebSocket routing decisions.
#_is_api_request
def _is_api_request(self, path: str) -> boolReturn True if this path should go to the app, not be proxied.
#close
async def close(self) -> None#_proxy_http
async def _proxy_http(self, scope: Scope, send: Send, *, body: bytes=b'') -> NoneForward an HTTP request to the Vite dev server.
The body parameter contains the pre-captured request body (already consumed from receive by the backend during the try-first phase).
#_proxy_ws
async def _proxy_ws(self, scope: Scope, receive: Receive, send: Send) -> NoneBidirectional WebSocket proxy to Vite (for HMR).
Uses the websockets library if available. Falls back to closing the connection with an error code if not installed.
#CORS Configuration for a Typical SPA
When building a single-page application with a separate frontend dev server (e.g., Vite on port 5173), you need to configure CORS to allow the frontend origin. The CORSMiddleware handles preflight OPTIONS requests automatically and injects the correct Access-Control-Allow-Origin, Access-Control-Allow-Methods, and Access-Control-Allow-Headers response headers on every cross-origin request:
from fastware import Router, create_app, AppConfig
router = Router()
# Option 1: Via AppConfig
app = create_app(
router,
config=AppConfig(
cors_origins=["http://localhost:5173"],
api_prefix="/api",
spa_fallback=Path("dist/index.html"),
),
)
# Option 2: Using CORSMiddleware directly for full control
from fastware.middleware import CORSMiddleware
app = create_app(
router,
middleware=[
lambda app: CORSMiddleware(
app,
allow_origins=["http://localhost:5173", "https://myapp.com"],
allow_methods=["GET", "POST", "PUT", "DELETE"],
allow_headers=["authorization", "content-type", "x-csrf-token"],
allow_credentials=True,
),
],
)When cors_origins is set on AppConfig, create_app applies CORSMiddleware automatically with default methods and headers. Use the direct middleware approach when you need to customize allowed methods or headers.
#ViteDevProxy Routing
The ViteDevProxy middleware uses a backend-first routing strategy for HTTP requests, forwarding unmatched paths to the Vite dev server so that frontend assets, HMR WebSocket connections, and backend API routes all work through a single origin without manual proxy configuration:
- Every HTTP request hits the fastware backend first.
- If the backend returns 404 (no route matched), the request is proxied to the Vite dev server.
- This means backend routes like
/healthor/metricswork without needing an API prefix.
The backend_prefixes parameter controls which additional paths are always routed to the backend for WebSocket connections (since WebSocket upgrades cannot be retried). By default, this includes ["/events"] for SSE endpoints.
from fastware import Router, create_app, AppConfig
router = Router()
# Vite dev server on port 5173
# Backend routes: /api/*, /events, /ws/*
app = create_app(
router,
config=AppConfig(
vite_dev_port=5173,
api_prefix="/api",
),
)
# Or with custom backend prefixes for WebSocket routing:
from fastware.middleware import ViteDevProxy
app = create_app(
router,
middleware=[
lambda app: ViteDevProxy(
app,
vite_port=5173,
api_prefix="/api",
backend_prefixes=["/events", "/ws", "/notifications"],
),
],
)For WebSocket connections, prefix-based routing is used: paths matching api_prefix or any backend_prefixes entry go to the backend; everything else is proxied to Vite (for HMR). HTTP requests use the try-backend-first approach regardless of path.