Core concepts

Middleware

Wrap handlers with focused request and response behavior.

What a wrapper does

Middleware is a function taking a handler and returning a handler. Request transformations run before the inner handler; response transformations run after its result. Put wrappers around the Ring handler before adapting it with ft-handler, or pass the wrapped Ring handler to server/listen!.

(ns application.forms
  (:require [fast-twitch.routing :as routing]
            [fast-twitch.middlewares.params :as params]
            [fast-twitch.middlewares.nested-params :as nested]
            [fast-twitch.middlewares.keyword-params :as keywords]))

(defn echo-fields [request]
  {:status 200 :headers {"content-type" "text/plain; charset=utf-8"}
   :body (pr-str (:params request))})

(def app
  (-> (routing/routes [{:pattern "/form" :method :post :handler echo-fields}]
                      (fn [_] (routing/not-found "Missing")))
      (keywords/wrap-keyword-params)
      (nested/wrap-nested-params)
      (params/wrap-params)))

This is the Ring application portion; use the startup guide or complete forms project to run it. A POST body user[name]=Ada becomes a nested keyword-keyed :params map. The last wrapper written, params, runs first: decoding → nesting → keyword conversion → routing → handler. Response processing unwinds in the opposite order. Changing the order can leave a handler with flat or undecoded parameters.

The shared common wrapper helpers support native Responses, response maps, Promises and callback handlers. A transform can be asynchronous. A three-argument callback handler must deliver through respond / raise; returning a value is not delivery.

Choose API or site defaults

(ns application.defaults
  (:require [fast-twitch.middlewares.defaults :as defaults]))

(defn handler [_]
  {:status 200 :headers {"content-type" "text/plain"} :body "Hello"})

(def api (defaults/wrap-defaults handler defaults/api-defaults))
(def site (defaults/wrap-defaults handler defaults/site-defaults))

wrap-defaults takes [handler options]; choose one of these application handlers for startup. The map is configuration, not a list of wrapper functions. False disables a configurable feature; enabled features accepting options use their own map. The source/reference has the full map and composition order.

Configuration path API default Site default
[:params :urlencoded], [:params :keywordize] true true
[:params :multipart], [:params :nested] Absent true
[:params :method-override] false false
:cookies Absent true
:session Absent Cookie name ft-session, HttpOnly, SameSite lax, flash enabled
[:security :anti-forgery] Absent Enabled with :safe-header "X-Ft-Anti-Forgery"
Request ID, size limit, rate limit, timeout false false
Not-modified, content length, content types true true
Default charset "utf-8" "utf-8"
Cache policy, absolute redirects, logging false false
CSP, SSL redirect, HSTS, proxy headers false false
X headers nosniff, sameorigin, X-XSS-Protection "0" Same

HEAD wrapping is always included by the current defaults response chain. JSON body decoding is not supplied by wrap-params or either defaults map: use a configured JSON listener or a dedicated bounded body reader. Site defaults are a starting configuration, not automatically persistent sessions or a trust policy for every deployment.

Parameters and uploads

Wrapper/API Request data and settings
params Query/URL-encoded parsing into string-keyed :query-params, :form-params, merged :params; the current :encoding option is passed through but ignored by the parser
multipart-params Native FormData parsing into :multipart-params and :params; repeated fields become vectors; current parser does not enforce a supplied upload byte-limit option
nested-params Bracketed field names become nested maps/vectors after parsing
keyword-params Converts supported parsed parameter keys to keywords recursively; values are not coerced
method-override A POST can select an allowed alternate method through its configured parameter/header

An upload field is {:filename ... :content-type ... :size ... :file native-file :tempfile native-file}. The native File is not a disk pathname. The upload project shows the wrapper order and Hiccup form; it reports metadata without storing the file. Decoding a body consumes it; downstream code should use parsed data rather than reading the raw body again.

Cookies, sessions and flash

(ns application.sessions
  (:require [fast-twitch.middlewares.session :as session]))

(def store (session/memory-store))
(def app
  (session/wrap-session
    (fn [request]
      (let [visits (inc (get-in request [:session :visits] 0))]
        {:status 200 :headers {"content-type" "text/plain"}
         :body (str "Visit " visits)
         :session (assoc (:session request) :visits visits)}))
    {:store store :cookie-name "visits"
     :cookie-attrs {:http-only true :same-site :lax}}))

Use the same cookie jar across requests to observe increasing visits. :store is an atom-backed map in this implementation, not an arbitrary database interface. :cookie-name defaults to "ft-session"; :root defaults to /; :cookie-attrs and per-response :session-cookie-attrs merge with default path and HttpOnly settings. Set Secure for HTTPS cookies where appropriate.

Omitting response :session leaves stored data unchanged. A response :session map replaces saved session data, so merge the request's session when preserving fields. Explicit :session nil deletes the session and emits an expiring cookie. Cookie values are separate from the session's contents. Default/injected memory stores belong to a process and are not shared across replicas or durable across restart.

Cookies middleware reads cookie headers into :cookies and serializes response :cookies into separate Set-Cookie entries. A cookie instruction contains a value and attributes such as path, max-age, secure, http-only and same-site. Flash middleware moves one-time data through the session: wrap it inside session loading/saving. Complete authenticated todo project.

Request controls and observability

Wrapper Settings / default behavior
request-id Request ID generation/propagation; inspect its configurable header and ID function in the reference
request-size-limit :max-bytes or numeric option; declared Content-Length only; default 413 error response; optional :error-response / :error-handler
rate-limit :requests-per-second / :rate default 10, :burst defaults to ceiling(rate), :store, :key-fn; default 429 with Retry-After 1; custom error hook receives request and result
timeout :timeout-ms / :ms default 30000; default 503; optional :error-response / :error-handler
logging Optional :logger and :event-fn; completion event rather than automatic stream-by-stream logging

A Content-Length check does not bound an undeclared/chunked body. Use actual bounded readers for that. Rate buckets default to an in-memory store per wrapped handler; multiple processes do not form one global limit. The default key uses real-ip, remote-addr or unknown, so all users share a key if the application supplies no useful identity.

Timeout bounds completion of a Promise/callback response and disposes a late losing response body. It does not preempt synchronous JavaScript, cancel arbitrary provider work or limit the lifetime of an already-returned SSE body. Connect upstream cancellation/deadlines in business logic.

Response, file and deployment wrappers

API Purpose and important constraint
common Header helpers, Fetch normalization, Promise-aware request/response transforms
content-type MIME inference and file sniffing; :mime-types overrides extension mappings
default-charset Adds a fallback charset to applicable text media types; wrapper accepts the charset string
content-length Measures supported bodies, including UTF-8 byte length; does not consume streams to measure them
head Executes GET behavior for HEAD and removes/disposes the body
not-modified Uses ETag/Last-Modified request validators to return bodyless 304 where applicable
absolute-redirects Rewrites redirect Location headers using current request URL information
cache-policy Header-policy memoization, not response-body caching; static-prefix policy and explicit dynamic policy
file Filesystem root and URL root, path checks, index/dotfile/header settings
resource Delegates resource serving to the filesystem file wrapper; not a JVM classpath reader
file-info File metadata/header enrichment around responses
proxy-headers Forwarded host/scheme/IP adaptation and :real-ip-headers; establish proxy trust outside this wrapper
ssl SSL redirect/HSTS through :ssl-redirect?, :hsts?, nested :hsts settings
content-security-policy Structured CSP config, serialization/validation and optional header-name override
x-headers :content-type-options, :frame-options, :xss-protection response headers

For caching, :max-age defaults to 31536000 and immutable is enabled unless false for matching static prefixes; only apply that long policy to versioned assets. Dynamic responses default to no-cache/no-store. Cache settings include :static-prefixes, :static-policy, :dynamic-policy, :cacheable?, :etag-fn, :key-fn, :backend, :max-entries; backends memoize policy maps.

HSTS options include max-age (default 31536000), include-subdomains? and preload?. Configure forwarded headers before components that use rewritten scheme/host. Header interpretation alone does not authenticate an untrusted proxy. File handling details.

Anti-forgery tokens and async rendering

The anti-forgery middleware puts the token on the request as :anti-forgery-token, stores it in the outgoing session, and binds *anti-forgery-token* and *anti-forgery-param-name* while invoking the handler. By default, submitted tokens are read from the __anti-forgery-token form parameter or the X-CSRF-Token / X-XSRF-Token headers.

Dynamic bindings do not automatically survive an await or a later JavaScript callback. Capture request values before awaiting, or use bound-anti-forgery-fn to capture the current bindings for a renderer:

(ns application.forms
  (:require [cljs.core :refer [await]]
            [fast-twitch.middlewares.anti-forgery :as anti-forgery]))

(defn ^:async render-after [request load-data render-form]
  (let [render (anti-forgery/bound-anti-forgery-fn render-form)]
    (render (await (load-data request)))))

Call render-after from the protected handler. load-data supplies the form's data; render-form synchronously renders the response with the captured token and parameter name. If the renderer also awaits, capture the values at its entry before that await. Compose session and parameter parsing outside anti-forgery so it receives the decoded session and submitted token, or use wrap-defaults with site-defaults.

Custom anti-forgery :read-token receives [request options]; :error-handler receives [request failure]. These hook arities also apply to the three-argument Ring callback path. Returning :session nil clears the session instead of restoring its anti-forgery token. The built-in session store is process-local; choose its lifetime with your application's deployment in mind.

Streaming caveats

The timeout middleware bounds response creation, not the stream lifetime, and disposes a late losing response body. Add an explicit upstream deadline and disconnect cancellation. Content-length now measures UTF-8 bytes for strings and sequential bodies, and byte lengths for buffers, typed views and Blobs; native streams remain unmeasured. HEAD and conditional-response wrappers remove the body and dispose the discarded stream. Check each wrapper's supported body types before adding it to a streaming handler.

Source revision a4eb73f96836d034c7a24fe09dc7d51e4c581f64

Built with Fast-Twitch · Integrant · Datastar

Search documentation

Search pages and API symbols

Start hereWelcome to Fast-TwitchRing HTTP applications and configured native clients and transports in ClojureScript.Start hereGetting startedCompile and run your first Fast-Twitch server.Core conceptsRequest & responseUnderstand the maps at the center of a Fast-Twitch application.Core conceptsRoutingMatch URL patterns and compose handlers.Core conceptsMiddlewareWrap handlers with focused request and response behavior.GuidesHTML & IntegrantCompose a UI system with Integrant and render Hiccup.