Middleware
Middleware in Nexios is a powerful feature that allows you to intercept, process, and modify requests and responses as they flow through your application. It acts as a pipeline, enabling you to implement cross-cutting concerns such as logging, authentication, validation, and response modification in a modular and reusable way. This documentation provides a comprehensive guide to understanding and using middleware in Nexios.
How Middleware Works
Middleware functions are executed in a sequence, forming a pipeline that processes incoming requests and outgoing responses. Each middleware function has access to the request (req
), response (res
), and a next
function to pass control to the next middleware or the final route handler.
Key Responsibilities of Middleware
- Modify the Request – Add headers, parse data, or inject additional context.
- Block or Allow Access – Enforce authentication, rate limiting, or other access controls.
- Modify the Response – Format responses, add headers, or compress data.
- Pass Control – Call
next()
to continue processing the request or terminate early.
Basic Middleware Example
Below is a simple example demonstrating how to define and use middleware in a Nexios application:
from nexios import NexiosApp
from datetime import datetime
app = NexiosApp()
# Middleware 1: Logging
async def my_logger(req, res, next):
print(f"Received request: {req.method} {req.path}")
await next() # Pass control to the next middleware or handler
# Middleware 2: Request Timing
async def request_time(req, res, next):
req.request_time = datetime.now() # Store request time in context
await next()
# Middleware 3: Cookie Validation
async def validate_cookies(req, res, next):
if "session_id" not in req.cookies:
return res.json({"error": "Missing session_id cookie"}, status_code=400)
await next()
# Add middleware to the application
app.add_middleware(my_logger)
app.add_middleware(request_time)
app.add_middleware(validate_cookies)
# Route Handler
@app.get("/")
async def hello_world(req, res):
return res.text("Hello, World!")
💡Tip
All code before await next()
is executed before the route handler.
Order of Execution
Middleware functions are executed in the order they are added. The flow of execution is as follows:
- Pre-Processing – Middleware functions execute before the route handler.
- Route Handler – The request is processed by the route handler.
- Post-Processing – Middleware functions execute after the route handler.
Incoming request
└──> Middleware 1 (logs)
└──> Middleware 2 (auth check)
└──> Route handler (e.g., /profile)
└──> Response is built
←──── Middleware 2 resumes (e.g., modify response)
←──── Middleware 1 resumes
←──── Final response sent
💡Tip
Middleware functions are executed in the order they are added. Ensure that middleware with dependencies (e.g., authentication before authorization) is added in the correct sequence.
What is cnext
?
In Nexios, middleware functions rely on a continuation callback (commonly called next, cnext, or callnext) to pass control to the next stage of the request pipeline. This parameter is crucial for request flow but its name is completely flexible — you're free to call it whatever makes sense for your codebase.
Class-Based Middleware
Nexios supports class-based middleware for better organization and reusability. A class-based middleware must inherit from BaseMiddleware
and implement the following methods:
process_request(req, res, cnext)
– Executed before the request reaches the handler.process_response(req, res)
– Executed after the handler has processed the request.
Example: Class-Based Middleware
from nexios.middlewares import BaseMiddleware
class ExampleMiddleware(BaseMiddleware):
async def process_request(self, req, res, cnext):
"""Executed before the request handler."""
print("Processing Request:", req.method, req.url)
await cnext(req, res) # Pass control to the next middleware or handler
async def process_response(self, req, res):
"""Executed after the request handler."""
print("Processing Response:", res.status_code)
return res # Must return the modified response
Method Breakdown
process_request(req, res, cnext)
- Used for pre-processing tasks like logging, authentication, or data injection.
- Must call
await cnext(req, res)
to continue processing.
process_response(req, res)
- Used for post-processing tasks like modifying the response or logging.
- Must return the modified
res
object.
Route-Specific Middleware
Route-specific middleware applies only to a particular route. This is useful for applying middleware logic to specific endpoints without affecting the entire application.
Example: Route-Specific Middleware
async def auth_middleware(req, res, cnext):
if not req.headers.get("Authorization"):
return res.json({"error": "Unauthorized"}, status_code=401)
await cnext(req, res)
@app.route("/profile", "GET", middlewares=[auth_middleware])
async def get_profile(req, res):
return res.json({"message": "Welcome to your profile!"})
⚙️ Execution Order:auth_middleware → get_profile handler → response sent
Router-Specific Middleware
Router-specific middleware applies to all routes under a specific router. This is useful for grouping middleware logic for a set of related routes.
Example: Router-Specific Middleware
admin_router = Router()
async def admin_auth(req, res, cnext):
if not req.headers.get("Admin-Token"):
return res.json({"error": "Forbidden"}, status_code=403)
await cnext(req, res)
admin_router.add_middleware(admin_auth) # Applies to all routes inside admin_router
@admin_router.route("/dashboard", "GET")
async def dashboard(req, res):
return res.json({"message": "Welcome to the admin dashboard!"})
app.mount_router("/admin", admin_router) # Mount router at "/admin"
Execution Order:admin_auth → dashboard handler → response sent
Using @use_for_route
Decorator
The @use_for_route
decorator binds a middleware function to specific routes or route patterns, ensuring that the middleware only executes when a matching route is accessed.
Example: @use_for_route
Decorator
from nexios.middleware.utils import use_for_route
@use_for_route("/dashboard")
async def log_middleware(req, res, cnext):
print(f"User accessed {req.path.url}")
await cnext(req, res) # Proceed to the next function (handler or middleware)
Always call await next()
in middleware to ensure the request continues processing. Failing to do so will block the request pipeline.
⚠️ Warning
Avoind modifying the request object in middleware. This can lead to unexpected behavior or security issues.
⚠️ Warning
Modifying the response object should be done after the request is processed. It's best to use the process_response
method of middleware or callnext
Raw ASGI Middleware
Nexios Allow you to use raw ASGI middleware. This can be useful for adding middleware that needs lower-level control over the ASGI protocol.
def raw_middleware(app):
async def middleware(scope, receive, send):
## Do something with scope, receive, send
await app(scope, receive, send)
return middleware
The app(scope, receive, send)
function is the next middleware in the chain
Adding raw middleware
app.wrap_asgi(raw_middleware)
💡Tip
The app
objects is an instance of NexiosApp
You can access the app
object in your middleware by calling app
.
Class Based Raw Middleware
class RawMiddleware:
def __init__(self, app):
self.app = app
async def __call__(self, scope, receive, send):
## Do something with scope, receive, send
await self.app(scope, receive, send)
Raw Middleware with args
class RawMiddleware:
def __init__(self, app, *args, **kwargs):
self.app = app
async def __call__(self, scope, receive, send):
## Do something with scope, receive, send
await self.app(scope, receive, send)
app.wrap_asgi(RawMiddleware, "arg1", "arg2")