Peter Amiri
06/26/2026, 5:27 AMhandle(request, next) returns a string; code before next() runs in, code after runs out.
• Why request is NOT the CFML request scope (it's a {params, route, pathInfo, method} struct) and the Adobe CF scope-shadow trap that follows.
• The lifecycle that changes everything: every middleware is a singleton shared across all concurrent requests for the app's lifetime — so any mutable state MUST be locked. Worked example with a cflock-guarded counter.
• Short-circuiting — return a string WITHOUT calling next() to skip the controller (how RateLimiter does its 429 and Cors does OPTIONS).
• Registration: global in config/settings.cfm vs route-scoped in config/routes.cfm, string-path vs object-instance, and why global always runs before route-scoped.
Three worked components: a request timer, a thread-safe audit logger, and a maintenance gate.