Routing
Match URL patterns and compose handlers.
Route definitions
Each route declares a URLPattern-compatible :pattern, a method, and a handler.
(def app
(routing/routes
[{:pattern "/" :method :get :handler home}
{:pattern "/hello/:name" :method :get :handler hello}]
not-found))
Path parameters
(defn hello [{:keys [path-params]}]
{:status 200 :headers {} :body (str "Hello " (:name path-params))})
Methods and fallbacks
Use a keyword such as :get, a string such as "POST", or a sequential collection such as [:get :head]. Omitting :method matches any method. The first matching route wins; otherwise the fallback handler runs. Keep a not-found handler that returns status 404. Keep route definitions together so the application’s URL structure can be reviewed in one place.
Rewritten request URLs
Routing matches the current request map's :uri, using :scheme, :server-name, :server-port and :query-string to construct the URL. Middleware can therefore rewrite :uri before dispatch without changing the original Fetch Request.
(defn wrap-legacy-home [handler]
(fn [request]
(handler (if (= "/legacy-home" (:uri request))
(assoc request :uri "/")
request))))
(def handler
(routing/ft-handler (wrap-legacy-home app)
{:protocol "HTTP/1.1" :remote-addr "127.0.0.1"}))
The underlying Fetch Request URL is used for route matching only when the map has no :uri.
Asynchronous routes
A route's :handler can return a response map, a native Fetch Response, or a Promise of either, including a native ^:async function. Use :async-handler for (request respond raise) callbacks; its return value is ignored. When calling the assembled routes with callbacks, rejected response Promises and errors from respond are forwarded to raise.
WebSocket routes
On Bun and Deno, an ordinary route can return a Ring WebSocket listener:
{:pattern "/socket"
:handler (fn [_]
{:ring.websocket/listener
{:on-message (fn [connection message]
(client/send! connection message))}})}
Require fast-twitch.client.core as client. Serve this route through the existing Fetch/routing lifecycle or server/listen!. Node's native server adapter returns HTTP 501 for upgrades; it supports the native WebSocket client. Native transports and runtime support.
Adapter wiring
Wrap the assembled app with routing/ft-handler before starting the runtime. Full routing API.
Complete small dispatch example
(ns application.routes
(:require [cljs.core :refer [await]]
[fast-twitch.routing :as routing]))
(def app
(routing/routes
[{:pattern "/hello/:name" :method [:get :head]
:handler (fn [{:keys [path-params]}]
(-> (routing/response (str "Hello, " (:name path-params)))
(routing/header "content-type" "text/plain")))}
{:pattern "/hello" :method :post
:handler (fn [_] (routing/status 204))}]
(fn [_] (routing/not-found "No matching route"))))
(def fetch-handler
(routing/ft-handler app {:protocol "HTTP/1.1" :remote-addr "unknown"}))
(defn ^:async check-route! []
(let [response (await (fetch-handler (js/Request. "http://localhost/hello/Ada")))]
(assert (= 200 (.-status response)))
(assert (= "Hello, Ada" (await (.text response))))))
Call check-route! in the compiled namespace, or start fetch-handler using the startup guide. Route matching itself needs no listening socket. The runtime must supply URLPattern. A HEAD method match alone does not strip the body; use wrap-head or a defaults chain for correct HEAD wire behavior.
Ordering and matching mistakes
Route order is declaration order, not a specificity ranking. Put a specific /hello/new route before /hello/:name if it needs different behavior. A method mismatch continues to later entries/fallback; the router does not generate a 405 or an Allow header automatically. A missing :method intentionally matches every method. URLPattern pathname matching is distinct from query parsing.
Use :handler for a synchronous response or Promise. Use :async-handler for exactly the callback contract. Do not supply both and rely on an implicit fallback: the callback entry takes precedence. The fallback is still a real function that must return/deliver a response.
Full HTML route project · Worker route projects · All route signatures.