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
def add_cors_headers(req: Any) # Continue to next middleware/handler
{
"continue": true,
"request": req
}
end
# order: 20
# scope_only: true
def authenticate(req: Any) let headers = req["headers"];
let api_key = "";
if has_key(headers, "X-Api-Key")
api_key = headers["X-Api-Key"];
end
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"
})
}
};
end
# Continue to handler
{
"continue": true,
"request": req
}
end
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
Requests flow through middleware layers before reaching your controller.
Step 1
Global Middleware
(Sorted by order)
Step 2
Scoped Middleware
(Specific to route)
Target
Controller Action
Return
Response sent to client
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", ->
get("/dashboard", "dashboard#index");
get("/profile", "users#profile");
post("/settings", "users#update_settings");
end);
# Multiple middleware
middleware(["authenticate", "admin_only"], ->
get("/admin", "admin#index");
get("/admin/users", "admin#users");
end);
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