2017-10-30 06:46:20 +00:00
# Data-driven Middleware
2017-09-18 05:30:03 +00:00
2017-12-04 06:36:11 +00:00
Ring [defines middleware ](https://github.com/ring-clojure/ring/wiki/Concepts#middleware ) as a function of type `handler & args => request => response` . It's relatively easy to understand and enables good performance. Downside is that the middleware-chain is just a opaque function, making things like debugging and composition hard. It's too easy to apply the middleware in wrong order.
2017-09-18 05:30:03 +00:00
2017-12-04 06:36:11 +00:00
Reitit defines middleware as data:
2017-09-18 05:30:03 +00:00
2017-12-04 06:36:11 +00:00
1. Middleware can be defined as first-class data entries
2018-02-11 17:15:25 +00:00
2. Middleware can be mounted as a [duct-style ](https://github.com/duct-framework/duct/wiki/Configuration ) vector (of middleware)
2019-05-22 16:58:03 +00:00
4. Middleware can be optimized & [compiled ](compiling_middleware.md ) against an endpoint
2017-12-04 06:36:11 +00:00
3. Middleware chain can be transformed by the router
2017-09-18 05:30:03 +00:00
2017-12-04 06:36:11 +00:00
## Middleware as data
2017-11-11 15:30:17 +00:00
2018-02-11 17:15:25 +00:00
All values in the `:middleware` vector in the route data are expanded into `reitit.middleware/Middleware` Records with using the `reitit.middleware/IntoMiddleware` Protocol. By default, functions, maps and `Middleware` records are allowed.
2017-11-11 15:30:17 +00:00
2017-11-11 15:50:27 +00:00
Records can have arbitrary keys, but the following keys have a special purpose:
2017-11-11 15:30:17 +00:00
2017-12-04 06:36:11 +00:00
| key | description |
| ---------------|-------------|
2017-12-29 09:41:12 +00:00
| `:name` | Name of the middleware as a qualified keyword
| `:spec` | `clojure.spec` definition for the route data, see [route data validation ](route_data_validation.md ) (optional)
2017-12-04 06:36:11 +00:00
| `:wrap` | The actual middleware function of `handler & args => request => response`
2017-12-04 21:58:05 +00:00
| `:compile` | Middleware compilation function, see [compiling middleware ](compiling_middleware.md ).
2017-11-11 15:30:17 +00:00
Middleware Records are accessible in their raw form in the compiled route results, thus available for inventories, creating api-docs etc.
2017-11-11 15:50:27 +00:00
For the actual request processing, the Records are unwrapped into normal functions and composed into a middleware function chain, yielding zero runtime penalty.
2017-11-11 15:30:17 +00:00
### Creating Middleware
The following produce identical middleware runtime function.
2017-12-04 06:36:11 +00:00
### Function
2017-11-11 15:30:17 +00:00
```clj
(defn wrap [handler id]
(fn [request]
(handler (update request ::acc (fnil conj []) id))))
```
2018-02-11 17:15:25 +00:00
### Map
```clj
(def wrap3
{:name ::wrap3
:description "Middleware that does things."
:wrap wrap})
```
2017-11-11 15:30:17 +00:00
### Record
2017-09-18 05:30:03 +00:00
```clj
2017-12-04 21:58:05 +00:00
(require '[reitit.middleware :as middleware])
2017-09-18 05:30:03 +00:00
(def wrap2
(middleware/create
{:name ::wrap2
2017-11-11 15:30:17 +00:00
:description "Middleware that does things."
2017-09-18 05:30:03 +00:00
:wrap wrap}))
```
2017-12-04 06:36:11 +00:00
## Using Middleware
`:middleware` is merged to endpoints by the `router` .
2017-11-11 15:30:17 +00:00
```clj
(require '[reitit.ring :as ring])
(defn handler [{:keys [::acc]}]
{:status 200, :body (conj acc :handler)})
(def app
(ring/ring-handler
(ring/router
["/api" {:middleware [[wrap 1] [wrap2 2]]}
["/ping" {:get {:middleware [[wrap3 3]]
:handler handler}}]])))
```
2017-12-04 06:36:11 +00:00
All the middleware are applied correctly:
2017-11-11 15:30:17 +00:00
```clj
(app {:request-method :get, :uri "/api/ping"})
; {:status 200, :body [1 2 3 :handler]}
```
2017-12-04 06:36:11 +00:00
## Compiling middleware
Middleware can be optimized against an endpoint using [middleware compilation ](compiling_middleware.md ).
2018-02-11 17:15:25 +00:00
## Ideas for the future
2017-11-11 15:30:17 +00:00
* Support Middleware dependency resolution with new keys `:requires` and `:provides` . Values are set of top-level keys of the request. e.g.
* `InjectUserIntoRequestMiddleware` requires `#{:session}` and provides `#{:user}`
* `AuthorizationMiddleware` requires `#{:user}`
2017-10-30 06:46:20 +00:00
2017-11-11 15:30:17 +00:00
Ideas welcome & see [issues ](https://github.com/metosin/reitit/issues ) for details.