Middleware
Filter HTTP requests and responses. Middleware enables cross-cutting concerns like authentication, logging, and CORS handling.
Request Logging
Soli includes built-in request logging at the server level with timing information:
[LOG] GET /users - 200 (1.234ms)
[LOG] POST /login - 302 (12.876ms)
[LOG] GET /missing - 404 (0.102ms)
To disable request logging, set the environment variable:
# Disable request logging
SOLI_REQUEST_LOG=false soli serve myapp
# Or
SOLI_REQUEST_LOG=0 soli serve myapp
Middleware Attributes
Middleware files in app/middleware/ are loaded automatically. Control behavior using special comment attributes:
| Attribute | Description |
|---|---|
| // order: N | Execution order (lower runs first, default: 100) |
| // global_only: true | Runs for ALL requests, cannot be scoped to specific routes |
| // scope_only: true | Only runs when explicitly scoped, never globally |
Creating Middleware
Middleware functions receive a request hash and return a result hash:
// order: 5
// global_only: true
fn add_cors_headers(req: Any) -> Any {
// Continue to next middleware/handler
return {
"continue": true,
"request": req
};
}
// order: 20
// scope_only: true
fn authenticate(req: Any) -> Any {
headers = req["headers"];
api_key = "";
if (has_key(headers, "X-Api-Key")) {
api_key = headers["X-Api-Key"];
}
if (api_key == "") {
// Short-circuit with error response
return {
"continue": false,
"response": {
"status": 401,
"headers": {"Content-Type": "application/json"},
"body": json_stringify({
"error": "Unauthorized",
"message": "API key required"
})
}
};
}
// Continue to handler
return {
"continue": true,
"request": req
};
}
Return Format
Middleware must return a hash with one of these formats:
{
"continue": true,
"request": req
}
{
"continue": false,
"response": {
"status": 401,
"body": "Unauthorized"
}
}
Execution Order
Each request flows through middleware layers before reaching your controller, then back out as a response.
order.middleware(...).Any middleware can short-circuit the chain by returning { "continue": false, "response": ... }, skipping later layers and the controller entirely.
Scoping Middleware to Routes
Use the middleware() function in routes to apply scope-only middleware:
// Public routes (no auth required)
get("/", "home#index");
get("/login", "auth#login");
// Protected routes (auth middleware applied)
middleware("authenticate", fn() {
get("/dashboard", "dashboard#index");
get("/profile", "users#profile");
post("/settings", "users#update_settings");
});
// Multiple middleware
middleware(["authenticate", "admin_only"], fn() {
get("/admin", "admin#index");
get("/admin/users", "admin#users");
});
Best Practices
- • Middleware files in
app/middleware/are loaded automatically — no imports needed - • Use
orderto control execution sequence (lower runs first) - • Use
global_only: truefor middleware that must run on every request (CORS, security headers) - • Use
scope_only: truefor authentication to prevent accidental global application - • Keep global middleware lightweight; expensive operations belong in scoped middleware