reitit-ring-defaults
reitit-ring-defaults copied to clipboard
Sensible data-driven Ring middleware defaults
reitit-ring-defaults
Lifts ring-defaults middleware into data-driven middleware, providing sensible defaults per route.
Rationale
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-handlerandcreate-file-handlerwill have no effect when ring-defaults installs awrap-resourceandwrap-filemiddleware - 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?
Installation
deps.edn:
com.fbeyer/reitit-ring-defaults {:mvn/version "0.1.0"}
Leiningen/Boot:
[com.fbeyer/reitit-ring-defaults "0.1.0"]
Basic usage
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.
Extended defaults
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)))
Warning on Session Middleware
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]])))
License
Copyright 2022 Ferdinand Beyer.
Distributed under the MIT License.