Skip to main content

Ring Router

Ring is a Clojure web applications library inspired by Python's WSGI and Ruby's Rack. By abstracting the details of HTTP into a simple, unified API, Ring allows web applications to be constructed of modular components that can be shared among a variety of applications, web servers, and web frameworks.

Read more about the Ring Concepts.

[metosin/reitit-ring "0.7.0-alpha7"]

reitit.ring/router

reitit.ring/router is a higher order router, which adds support for :request-method based routing, handlers and middleware.

It accepts the following options:

keydescription
:reitit.middleware/transformFunction of [Middleware] => [Middleware] to transform the expanded Middleware (default: identity).
:reitit.middleware/registryMap of keyword => IntoMiddleware to replace keyword references into Middleware
:reitit.ring/default-options-endpointDefault endpoint for :options method (default: default-options-endpoint)

Example router:

(require '[reitit.ring :as ring])

(defn handler [_]
{:status 200, :body "ok"})

(def router
(ring/router
["/ping" {:get handler}]))

Match contains :result compiled by reitit.ring/router:

(require '[reitit.core :as r])

(r/match-by-path router "/ping")
;#Match{:template "/ping"
; :data {:get {:handler #object[...]}}
; :result #Methods{:get #Endpoint{...}
; :options #Endpoint{...}}
; :path-params {}
; :path "/ping"}

reitit.ring/ring-handler

Given a router from reitit.ring/router, optional default-handler & options, ring-handler function will return a valid ring handler supporting both synchronous and asynchronous request handling. The following options are available:

keydescription
:middlewareOptional sequence of middlewares that wrap the ring-handler
:inject-match?Boolean to inject match into request under :reitit.core/match key (default true)
:inject-router?Boolean to inject router into request under :reitit.core/router key (default true)

Simple Ring app:

(def app (ring/ring-handler router))

Applying the handler:

(app {:request-method :get, :uri "/favicon.ico"})
; nil
(app {:request-method :get, :uri "/ping"})
; {:status 200, :body "ok"}

The router can be accessed via get-router:

(-> app (ring/get-router) (r/compiled-routes))
;[["/ping"
; {:handler #object[...]}
; #Methods{:get #Endpoint{:data {:handler #object[...]}
; :handler #object[...]
; :middleware []}
; :options #Endpoint{:data {:handler #object[...]}
; :handler #object[...]
; :middleware []}}]]

Request-method based routing

Handlers can be placed either to the top-level (all methods) or under a specific method (:get, :head, :patch, :delete, :options, :post, :put or :trace). Top-level handler is used if request-method based handler is not found.

By default, the :options route is generated for all paths - to enable thing like CORS.

(def app
(ring/ring-handler
(ring/router
[["/all" handler]
["/ping" {:name ::ping
:get handler
:post handler}]])))

Top-level handler catches all methods:

(app {:request-method :delete, :uri "/all"})
; {:status 200, :body "ok"}

Method-level handler catches only the method:

(app {:request-method :get, :uri "/ping"})
; {:status 200, :body "ok"}

(app {:request-method :put, :uri "/ping"})
; nil

By default, :options is also supported (see router options to change this):

(app {:request-method :options, :uri "/ping"})
; {:status 200, :body ""}

Name-based reverse routing:

(-> app
(ring/get-router)
(r/match-by-name ::ping)
(r/match->path))
; "/ping"

Middleware

Middleware can be mounted using a :middleware key - either to top-level or under request method submap. Its value should be a vector of reitit.middleware/IntoMiddleware values. These include:

  1. normal ring middleware function handler -> request -> response
  2. vector of middleware function [handler args*] -> request -> response and it's arguments
  3. a data-driven middleware record or a map
  4. a Keyword name, to lookup the middleware from a Middleware Registry

A middleware and a handler:

(defn wrap [handler id]
(fn [request]
(handler (update request ::acc (fnil conj []) id))))

(defn handler [{::keys [acc]}]
{:status 200, :body (conj acc :handler)})

App with nested middleware:

(def app
(ring/ring-handler
(ring/router
;; a middleware function
["/api" {:middleware [#(wrap % :api)]}
["/ping" handler]
;; a middleware vector at top level
["/admin" {:middleware [[wrap :admin]]}
["/db" {:middleware [[wrap :db]]
;; a middleware vector at under a method
:delete {:middleware [[wrap :delete]]
:handler handler}}]]])))

Middleware is applied correctly:

(app {:request-method :delete, :uri "/api/ping"})
; {:status 200, :body [:api :handler]}
(app {:request-method :delete, :uri "/api/admin/db"})
; {:status 200, :body [:api :admin :db :delete :handler]}

Top-level middleware, applied before any routing is done:

(def app
(ring/ring-handler
(ring/router
["/api" {:middleware [[mw :api]]}
["/get" {:get handler}]])
nil
{:middleware [[mw :top]]}))

(app {:request-method :get, :uri "/api/get"})
; {:status 200, :body [:top :api :ok]}