Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Serving app and api routes with different middleware using Ring and Compojure

I have a ring+compojure application and I want to apply different middleware depending on whether the route is part of the web application or part of the api (which is json based).

I found some answers to this question on stack overflow and other forums, but these answers seem more complicated than the solution I've been using. I wanted to know if there are drawbacks with how I'm doing it and what I may be missing in my solution. A very simplified version of what I'm doing is

  (defroutes app-routes
    (GET "/" [req] dump-req)
    (route/not-found "Not Found"))

(defroutes api-routes
  (GET "/api" [req] dump-req))

(def app
  (routes (-> api-routes
              (wrap-defaults api-defaults))
          (-> app-routes
              (wrap-defaults site-defaults))))

Note that there is more middleware than I have shown here.

The only 'restriction' I've encountered is that as the app-routes has the not-found route, it needs to come last or it will be triggered before finding the api routes.

This seems simpler and more flexible than some of the other solutions I've found, which appear to either use additional conditional middleware, such as ring.middleware.conditional or what seems to me as more complex routing definitions where there is an additional defroutes layer and the need to define defroutes with ANY "*" etc.

I suspect there is something subtle I'm missing here and while my approach seems to work, it will cause unexpected behaviour or results in some situations etc.

like image 614
Tim X Avatar asked Jan 19 '15 02:01

Tim X


Video Answer


1 Answers

You are correct, the ordering matters and there is a subtlety you are missing - the middleware you apply to api-routes is executed for all requests.

Consider this code:

(defn wrap-app-middleware
  [handler]
  (fn [req]
    (println "App Middleware")
    (handler req)))

(defn wrap-api-middleware
  [handler]
  (fn [req]
    (println "API Middleware")
    (handler req)))

(defroutes app-routes
  (GET "/" _ "App")
  (route/not-found "Not Found"))

(defroutes api-routes
  (GET "/api" _ "API"))

(def app
  (routes (-> api-routes
              (wrap-api-middleware))
          (-> app-routes
              (wrap-app-middleware))))

and repl session:

> (require '[ring.mock.request :as mock])
> (app (mock/request :get "/api"))
API Middleware
...
> (app (mock/request :get "/"))
API Middleware
App Middleware
...

Compojure has a nice feature and helper that applies middleware to routes after they have been matched - wrap-routes

(def app
  (routes (-> api-routes
              (wrap-routes wrap-api-middleware))
          (-> app-routes
              (wrap-routes wrap-app-middleware))
          (route/not-found "Not Found")))

> (app (mock/request :get "/api"))
API Middleware
...
> (app (mock/request :get "/"))
App Middleware
...
like image 141
Kyle Avatar answered Oct 08 '22 18:10

Kyle