Middleware System in APIFromAnything
This guide covers the middleware system in APIFromAnything, which allows you to process requests and responses in a flexible and modular way.
Introduction to Middleware
Middleware components are functions or classes that have access to the request and response objects in your API’s request-response cycle. They can:
Execute code before or after the request is processed
Modify the request or response objects
End the request-response cycle early
Call the next middleware in the stack
Middleware is executed in the order it is added to the API, forming a “middleware stack”.
Adding Middleware to Your API
from apifrom import API
from apifrom.middleware import CacheMiddleware, RateLimitMiddleware, CORSMiddleware
# Create an API instance
app = API(
title="Middleware Example",
description="An example of using middleware",
version="1.0.0"
)
# Add middleware to the API
app.add_middleware(CacheMiddleware(ttl=60)) # Cache responses for 60 seconds
app.add_middleware(RateLimitMiddleware(limit=100, window=60)) # 100 requests per minute
app.add_middleware(CORSMiddleware(allow_origins=["https://example.com"])) # CORS support
Built-in Middleware Components
APIFromAnything includes several built-in middleware components:
CacheMiddleware
Caches API responses with configurable TTL:
from apifrom import API
from apifrom.middleware import CacheMiddleware
app = API()
# Add cache middleware
app.add_middleware(
CacheMiddleware(
ttl=60, # Cache TTL in seconds
max_size=1000, # Maximum number of cached responses
key_builder=None, # Custom function to build cache keys
storage="memory", # Storage backend (memory, redis)
redis_url=None, # Redis URL (if using redis storage)
include_query_params=True, # Include query parameters in cache key
include_headers=False, # Include headers in cache key
cache_control=True, # Add Cache-Control headers to responses
)
)
RateLimitMiddleware
Limits the number of requests a client can make:
from apifrom import API
from apifrom.middleware import RateLimitMiddleware
app = API()
# Add rate limit middleware
app.add_middleware(
RateLimitMiddleware(
limit=100, # Maximum requests
window=60, # Time window in seconds
key_func=lambda request: request.client.host, # Function to extract the client key
storage="memory", # Storage backend (memory, redis)
redis_url=None, # Redis URL (if using redis storage)
headers=True, # Add rate limit headers to responses
)
)
CORSMiddleware
Handles Cross-Origin Resource Sharing (CORS) headers:
from apifrom import API
from apifrom.middleware import CORSMiddleware
app = API()
# Add CORS middleware
app.add_middleware(
CORSMiddleware(
allow_origins=["https://example.com", "https://app.example.com"],
allow_methods=["GET", "POST", "PUT", "DELETE", "OPTIONS"],
allow_headers=["Content-Type", "Authorization", "X-Custom-Header"],
allow_credentials=True,
expose_headers=["X-RateLimit-Limit", "X-RateLimit-Remaining"],
max_age=3600, # Cache preflight requests for 1 hour
)
)
SecurityHeadersMiddleware
Adds security headers to responses:
from apifrom import API
from apifrom.middleware import SecurityHeadersMiddleware
app = API()
# Add security headers middleware
app.add_middleware(
SecurityHeadersMiddleware(
# Content Security Policy
csp={
"default-src": ["'self'"],
"script-src": ["'self'", "https://cdn.example.com"],
"style-src": ["'self'", "'unsafe-inline'"],
"img-src": ["'self'", "data:"],
"font-src": ["'self'", "https://fonts.googleapis.com"],
"connect-src": ["'self'", "https://api.example.com"],
"object-src": ["'none'"],
"frame-ancestors": ["'none'"],
"upgrade-insecure-requests": True,
"block-all-mixed-content": True,
},
# HTTP Strict Transport Security
hsts={
"max-age": 31536000, # 1 year
"includeSubDomains": True,
"preload": True,
},
# X-Content-Type-Options
x_content_type_options="nosniff",
# X-Frame-Options
x_frame_options="DENY",
# X-XSS-Protection
x_xss_protection="1; mode=block",
# Referrer-Policy
referrer_policy="strict-origin-when-cross-origin",
# Permissions-Policy
permissions_policy={
"geolocation": ["self"],
"microphone": [],
"camera": [],
"payment": [],
},
)
)
CSRFMiddleware
Protects against Cross-Site Request Forgery attacks:
from apifrom import API
from apifrom.middleware import CSRFMiddleware
app = API()
# Add CSRF middleware
app.add_middleware(
CSRFMiddleware(
secret="your-csrf-secret",
cookie_name="csrf_token",
header_name="X-CSRF-Token",
secure=True, # Only send cookie over HTTPS
samesite="strict", # Cookie same-site policy
methods=["POST", "PUT", "DELETE", "PATCH"], # Methods to protect
)
)
XSSProtectionMiddleware
Protects against Cross-Site Scripting attacks:
from apifrom import API
from apifrom.middleware import XSSProtectionMiddleware
app = API()
# Add XSS protection middleware
app.add_middleware(
XSSProtectionMiddleware(
sanitize_html=True, # Sanitize HTML in responses
sanitize_json=True, # Sanitize JSON in responses
sanitize_headers=True, # Sanitize headers in responses
)
)
ErrorHandlingMiddleware
Catches and formats exceptions:
from apifrom import API
from apifrom.middleware import ErrorHandlingMiddleware
app = API()
# Add error handling middleware
app.add_middleware(
ErrorHandlingMiddleware(
debug=True, # Include debug information in development
include_traceback=True, # Include tracebacks in error responses
log_exceptions=True, # Log exceptions to the console
)
)
LoggingMiddleware
Logs requests and responses:
from apifrom import API
from apifrom.middleware import LoggingMiddleware
app = API()
# Add logging middleware
app.add_middleware(
LoggingMiddleware(
level="INFO", # Logging level
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", # Log format
log_request_headers=True, # Log request headers
log_request_body=False, # Log request body
log_response_headers=True, # Log response headers
log_response_body=False, # Log response body
)
)
CompressionMiddleware
Compresses responses:
from apifrom import API
from apifrom.middleware import CompressionMiddleware
app = API()
# Add compression middleware
app.add_middleware(
CompressionMiddleware(
minimum_size=1000, # Minimum response size to compress
level=9, # Compression level (1-9)
algorithms=["gzip", "deflate", "br"], # Supported compression algorithms
)
)
AuthenticationMiddleware
Handles authentication:
from apifrom import API
from apifrom.middleware import AuthenticationMiddleware
from apifrom.security import JWTAuthenticator, APIKeyAuthenticator
app = API()
# Add authentication middleware
app.add_middleware(
AuthenticationMiddleware(
authenticators=[
JWTAuthenticator(
secret="your-secret-key",
algorithm="HS256",
),
APIKeyAuthenticator(
api_keys={"api-key-1": ["read"]},
),
],
require_auth=False, # If True, all routes require authentication
)
)
Creating Custom Middleware
You can create your own middleware by extending the Middleware class:
from apifrom.middleware import Middleware
from apifrom.core.request import Request
from apifrom.core.response import Response
from typing import Callable, Awaitable
class LoggingMiddleware(Middleware):
async def dispatch(
self,
request: Request,
call_next: Callable[[Request], Awaitable[Response]]
) -> Response:
print(f"Request: {request.method} {request.url.path}")
# Process the request through the next middleware or endpoint
response = await call_next(request)
print(f"Response: {response.status_code}")
return response
# Add the custom middleware to the API
app.add_middleware(LoggingMiddleware())
Middleware with Configuration
You can create middleware with configuration options:
from apifrom.middleware import Middleware
from apifrom.core.request import Request
from apifrom.core.response import Response
from typing import Callable, Awaitable, List, Optional
class CustomHeadersMiddleware(Middleware):
def __init__(
self,
headers: dict,
override: bool = False,
exclude_paths: Optional[List[str]] = None
):
self.headers = headers
self.override = override
self.exclude_paths = exclude_paths or []
async def dispatch(
self,
request: Request,
call_next: Callable[[Request], Awaitable[Response]]
) -> Response:
# Process the request through the next middleware or endpoint
response = await call_next(request)
# Skip excluded paths
if any(request.url.path.startswith(path) for path in self.exclude_paths):
return response
# Add custom headers to the response
for name, value in self.headers.items():
if self.override or name not in response.headers:
response.headers[name] = value
return response
# Add the custom middleware to the API
app.add_middleware(
CustomHeadersMiddleware(
headers={"X-Custom-Header": "Value"},
override=True,
exclude_paths=["/health", "/metrics"]
)
)
Middleware with Early Response
You can create middleware that returns a response early:
from apifrom.middleware import Middleware
from apifrom.core.request import Request
from apifrom.core.response import Response
from typing import Callable, Awaitable, List
class MaintenanceModeMiddleware(Middleware):
def __init__(
self,
enabled: bool = False,
message: str = "The service is under maintenance",
status_code: int = 503,
exclude_paths: List[str] = None
):
self.enabled = enabled
self.message = message
self.status_code = status_code
self.exclude_paths = exclude_paths or []
async def dispatch(
self,
request: Request,
call_next: Callable[[Request], Awaitable[Response]]
) -> Response:
# Skip excluded paths
if any(request.url.path.startswith(path) for path in self.exclude_paths):
return await call_next(request)
# Return early if maintenance mode is enabled
if self.enabled:
return Response(
content={"message": self.message},
status_code=self.status_code,
headers={"Retry-After": "3600"}
)
# Process the request through the next middleware or endpoint
return await call_next(request)
# Add the custom middleware to the API
app.add_middleware(
MaintenanceModeMiddleware(
enabled=True,
message="We are performing scheduled maintenance. Please try again later.",
exclude_paths=["/health", "/status"]
)
)
Middleware Execution Order
Middleware is executed in the order it is added to the API:
from apifrom import API
from apifrom.middleware import (
LoggingMiddleware,
CORSMiddleware,
SecurityHeadersMiddleware,
CompressionMiddleware,
CacheMiddleware,
RateLimitMiddleware,
ErrorHandlingMiddleware
)
app = API()
# Add middleware in the desired order
app.add_middleware(LoggingMiddleware()) # 1. Log the request
app.add_middleware(CORSMiddleware()) # 2. Handle CORS
app.add_middleware(SecurityHeadersMiddleware()) # 3. Add security headers
app.add_middleware(CompressionMiddleware()) # 4. Compress the response
app.add_middleware(CacheMiddleware()) # 5. Cache the response
app.add_middleware(RateLimitMiddleware()) # 6. Rate limit the request
app.add_middleware(ErrorHandlingMiddleware()) # 7. Handle errors
The middleware stack is executed in the following order:
The request flows through the middleware stack from top to bottom.
The endpoint handler is called.
The response flows through the middleware stack from bottom to top.
Middleware Best Practices
1. Keep Middleware Focused
Each middleware should have a single responsibility:
# Good: Focused middleware
app.add_middleware(LoggingMiddleware())
app.add_middleware(CORSMiddleware())
app.add_middleware(CacheMiddleware())
# Bad: Middleware with multiple responsibilities
class DoEverythingMiddleware(Middleware):
async def dispatch(self, request, call_next):
# Log the request
# Handle CORS
# Cache the response
# ...
2. Order Middleware Correctly
The order of middleware matters:
# Good: Error handling middleware is added last
app.add_middleware(LoggingMiddleware())
app.add_middleware(CORSMiddleware())
app.add_middleware(ErrorHandlingMiddleware())
# Bad: Error handling middleware is added first
app.add_middleware(ErrorHandlingMiddleware())
app.add_middleware(LoggingMiddleware())
app.add_middleware(CORSMiddleware())
3. Use Middleware for Cross-Cutting Concerns
Use middleware for concerns that apply to multiple endpoints:
# Good: Use middleware for cross-cutting concerns
app.add_middleware(LoggingMiddleware())
app.add_middleware(CORSMiddleware())
app.add_middleware(AuthenticationMiddleware())
# Bad: Implement cross-cutting concerns in each endpoint
@api(route="/users", method="GET")
def get_users(request):
# Log the request
# Handle CORS
# Authenticate the user
# ...
4. Make Middleware Configurable
Make middleware configurable to support different use cases:
# Good: Configurable middleware
app.add_middleware(
CacheMiddleware(
ttl=60,
storage="redis",
redis_url="redis://localhost:6379/0"
)
)
# Bad: Hardcoded middleware
class HardcodedCacheMiddleware(Middleware):
async def dispatch(self, request, call_next):
# Hardcoded TTL, storage, etc.
# ...
5. Document Middleware
Document middleware to make it easier to use:
class DocumentedMiddleware(Middleware):
"""
A middleware that does something useful.
Args:
option1: Description of option1
option2: Description of option2
"""
def __init__(self, option1, option2):
self.option1 = option1
self.option2 = option2
async def dispatch(self, request, call_next):
"""
Process the request and response.
Args:
request: The request object
call_next: The next middleware in the stack
Returns:
The response object
"""
# ...
Conclusion
The middleware system in APIFromAnything provides a powerful way to process requests and responses in a flexible and modular way. By using built-in middleware components and creating custom middleware, you can add functionality to your API without modifying your endpoint handlers.