Request & response
Understand the maps at the center of a Fast-Twitch application.
Request maps
ft-handler converts a Fetch Request into an application map. Important fields include :request-method, :uri, :headers, :scheme, :server-name, :server-port, and optional :body, :query-string, :path-params.
Incoming request headers have lowercase keyword keys, such as :content-type; response headers use string names. Keep :protocol and :remote-addr in the options passed to ft-handler. The original request remains available as ::routing/request.
Inspect one request
For POST http://127.0.0.1:6464/hello/Ada?topic=clojure, the native adapter supplies these values before parameter middleware:
| Field | Shape / example |
|---|---|
:request-method |
Lowercase keyword, :post |
:uri |
Pathname string, "/hello/Ada" |
:query-string |
String without ?, "topic=clojure"; absent for empty query |
:headers |
Lowercase keyword keys, e.g. {:content-type "text/plain"} |
:body |
Native readable stream if present; not already a text string |
:scheme |
Keyword such as :http or :https |
:server-name, :server-port |
Host string and numeric port; default URL port 80/443 if omitted |
:protocol, :remote-addr |
Values required in Fetch adapter options |
:fast-twitch.routing/request |
Original native Fetch Request |
:path-params |
Named pathname groups, added when a route matches, e.g. {:name "Ada"} |
:query-params, :form-params, :params |
Added by parsing middleware, not by URL matching |
Destructuring chooses keys from these maps; it does not parse a stream or convert "42" to 42. Parameter middleware uses string keys before optional keyword conversion. Complete requests/forms example.
Response maps
{:status 200
:headers {"Content-Type" "application/json"}
:body "{\"ok\":true}"}
A response body can be a string or a streaming body supported by the Fetch runtime. Sequential bodies are concatenated by the adapter.
server/response also constructs a response map with explicit status, headers or JSON encoding:
(server/response {"ok" true}
{:codec :json :status 201})
Require fast-twitch.server.core as server for this helper. The JSON codec uses string-keyed maps and ordinary JSON values. A plain response map does not automatically encode a ClojureScript map in :body.
Async handlers
Handlers can return a JavaScript Promise resolving to a response map. ClojureScript's native ^:async functions produce that Promise; use await to read a Fetch body or wait for other asynchronous work.
(ns application.handlers
(:require [cljs.core :refer [await]]
[fast-twitch.routing :as routing]))
(defn ^:async echo [request]
(let [body (await (.text (::routing/request request)))]
{:status 200
:headers {"Content-Type" "text/plain; charset=utf-8"}
:body body}))
Use the normal ft-handler options for these Promise-returning handlers. The :async? true option selects the separate three-argument callback contract (handler request respond raise); it is not needed for ^:async functions. In that contract, the return value is ignored: deliver the result through respond or raise. Routes can also declare an :async-handler that uses those callbacks.
Return an SSE response immediately and do asynchronous work inside the stream to deliver an early status event.
Fetch boundary
The underlying Fetch Request is available at :fast-twitch.routing/request. Read its body once. If several middleware functions need decoded data, decode it in a single shared layer.
Handlers may also return native Fetch Response objects. Fast-Twitch normalizes them before response middleware, preserving an unchanged native response and its stream. Responses returned by client/request! retain a :fast-twitch.routing/response carrier; status, header and body edits on ordinary responses are applied at the Fetch boundary. Do not reuse a consumed or locked body. Editing special native responses such as opaque responses is rejected.
For JSON request decoding at server startup, use server/listen! with {:codec :json :streaming {:max-bytes 65536}}. With its default :native codec, bodies remain native streams and :streaming is not accepted. These configured helpers complement the existing Fetch adapter. Configured client/server guide.
Header and body rules
Header names are case-insensitive. The adapter accepts string or keyword response header names. Repeated Set-Cookie values use a sequence and remain separate header lines; do not join them with commas. Use common/header-value for case-independent lookups in middleware.
Native response bodies include strings, binary values, Blobs/Files and readable streams. The adapter handles sequential Ring bodies; it does not turn an arbitrary ClojureScript data map into JSON. Use an explicit JSON codec. Native HTTP forbids bodies on status 204, 205 and 304; construct bodyless responses accordingly. A Promise-returning handler must settle to a valid response, not nil.
Forwarding preserves resources
A native response map contains its original Response carrier and projection. Forward it unchanged to retain the original stream; edit status/headers/body through the map when required. Consumed/locked bodies cannot be forwarded again. An unknown fabricated carrier is rejected, and opaque/special native responses cannot be arbitrarily edited. Full forwarding example.
Decide where errors become responses
A transport rejection, malformed JSON or application exception is not automatically the same as an HTTP 400/500 response. Catch at the handler or shared application boundary and return the chosen status. A remote HTTP 404 is a response whose status the client can inspect. Callback handlers report failures through raise, while Promise-returning handlers reject. Native failure details.