Lifts ring-defaults middleware into data-driven middleware, providing sensible defaults per route.
Reitit provides advanced middleware capabilities and comes with a small number of "standard" middleware. For many common tasks such as session management, additional middleware is required.
Many people use wrap-defaults
from ring-defaults as a sensible default
chain. However, some middleware included in site-defaults
will duplicate
or conflict with an idiomatic Reitit setup:
- Static resources with
create-resource-handler
andcreate-file-handler
will have no effect when ring-defaults installs awrap-resource
andwrap-file
middleware - Duplicate parameter and multi-part handling
The result is sub-optimal performance and developer's confusion when requests are not handled as expected.
Furthermore, it is not obvious at which place in Reitit's configuration
wrap-defaults
should go: on the ring-handler
, in global router data,
per route?
deps.edn:
com.fbeyer/reitit-ring-defaults {:mvn/version "0.1.0"}
Leiningen/Boot:
[com.fbeyer/reitit-ring-defaults "0.1.0"]
The ring-defaults-middleware
is designed as a replacement for wrap-defaults
.
In order to have any effect, you will need to add a :defaults
key to your
route data. A good starting point is one of the curated configurations
provided by ring-defaults, such as api-defaults
:
(require '[reitit.ring :as ring]
'[reitit.ring.middleware.defaults :refer [ring-defaults-middleware]]
'[ring.middleware.defaults :refer [api-defaults]])
;; ...
(def app
(ring/ring-handler
(ring/router
["/api"
{:middleware ring-defaults-middleware
:defaults api-defaults}
["/ping" handler]
["/pong" handler]]
routes)))
You can treat the :defaults
data like any other route data: You can specify
it globally or per-route, and route configurations will be merged with parent
configurations.
Any middleware not configured per route will not mount, and have zero runtime impact.
Reitit provides excellent support for content negotiation and coercion, as well as exception handling.
Since it is very common to use these, there is a defaults-middleware
that includes additional Reitit middleware as a sensible default.
(require '[muuntaja.core :as muuntaja]
'[reitit.coercion.malli]
'[reitit.ring.middleware.defaults :refer [defaults-middleware]])
(def app
(ring/ring-handler
(ring/router
["/api"
{:middleware defaults-middleware
:defaults (-> api-defaults
;; Enable exception middleware. You can also add custom
;; handlers in the [:exception handlers] key and they
;; will be passed to create-exception-middleware.
(assoc :exception true))
;; Muuntaja instance for content negotiation
:muuntaja muuntaja/instance
;; Request and response coercion -- using Malli in this case.
:coercion reitit.coercion.malli/coercion}
["/ping" handler]
["/pong" handler]]
routes)))
Like wrap-defaults
, reitit-defaults-middleware includes Ring's wrap-session
,
and using its default configuration in Reitit route data will have surprising
effects, as Reitit will mount one instance per route.
The recommended solution with reitit-defaults-middleware is to explicitly configure a session store. Since the default in-memory store will not be suitable for non-trivial production deployments, you will want to do that anyway.
(require '[ring.middleware.session.memory :as memory])
;; single instance
(def session-store (memory/memory-store))
;; inside, with shared store
(def app
(ring/ring-handler
(ring/router
["/api"
{:middleware ring-defaults-middleware
:defaults (-> site-defaults
(assoc-in [:session :store] session-store))}
["/ping" handler]
["/pong" handler]])))
Copyright 2022 Ferdinand Beyer.
Distributed under the MIT License.