(iterate think thoughts)

what's new in lib-noir

25 May, 2013

It's been nearly a year since lib-noir was split out into a stand-alone library. During this time the work on it has continued at a steady pace. There have been numerous bug fixes and many new features have been added to the library.

Many of these come either from user suggestions or contributions. So, if there is something that you'd like to see improved don't hesitate to submit an issue or make a pull request.

In this post I'd like to highlight some of the major new features that have been recently added.


The app-handler in noir.util.middleware now accepts optional:middleware and :access-rules parameters.

Since the outer middleware is evaluated first, if you wrap the app-handler in custom middleware it will execute before any of the standard middleware is executed. This is a problem if you wish to get access to things like the session, eg:

(defn log-user-in-session [handler]
  (fn [req]
    (timbre/info (session/get :user))
    (handler req)))

(def app (-> (middleware/app-handler all-routes)

If we try to run our app with the above handler we'll get the following exception:

java.lang.ClassCastException: clojure.lang.Var$Unbound cannot be cast to java.util.concurrent.Future

This happens due to the fact that noir.session uses the *noir-session* dynamic variable to keep track of the session. This variable is bound by the wrap-noir-session middleware. Since the log-user-in-session executes before it, the session is not yet bound.

The :middleware key allows specifying a vector containing custom middleware to wrap the handler before the standard middleware:

(def app (middleware/app-handler all-routes
          :middleware [log-user-in-session]))

Now, the log-user-in-session will be called after the wrap-noir-session is called and work as expected.

The :access-rules key allows specifying the access rules for the wrap-access-rules middleware. Each set of rules should be specified as a vector with the contents matching the wrap-access-rules arguments:

(defn private-pages [method url params]    
    (session/get :user-id))

(def app (middleware/app-handler all-routes 
          [[{:redirect "/unauthorized"} private-pages]]))

There's also a new middleware wrapper called wrap-rewrites that allows rewriting URIs based on regex.

The rewrite rules should be supplied as pairs of the regex and the string the matching URL should be rewritten with. The first regex that matches the request's URI will cause it to be replaced with its corresponding string before calling the wrapped handler:

(wrap-rewrites handler #"/foo" "/bar")
Above, all occurances of the/foo URI will be replaced with /bar.


There's now a noir.util.route/def-restricted-routes macro for creating groups of restricted routes. Where before you had to do something like this:

(defroutes private-routes
  (restricted GET "/route1" [] handler1)
  (restricted GET "/route2" [] handler2)
  (restricted GET "/route3" [] handler3)
  (restricted GET "/route4" [] handler4))

you can now simply do:

(def-restricted-routes private-routes
  (GET "/route1" [] handler1)
  (GET "/route2" [] handler2)
  (GET "/route3" [] handler3)
  (GET "/route4" [] handler4))

The macro will automatically mark all the routes as restricted for you.

Finally, the access rules used to control the restricted routes are more flexible now as well. The redirect target can now point to a function as well as a string, eg:

(def app (middleware/app-handler all-routes 
             (fn [] 
              (println "redirecting") "/unauthorized")} 

As always, Luminus provides the latest lib-noir, so all the new features are available there as well.

tags luminusnoirclojure


28 May, 2013 - anonymous

documentation format of topics is not displaying properly

28 May, 2013 - Yogthos

Are you referring to the Luminus site, and what browser/os are you using, what's not displaying correctly?

04 Jun, 2013 - anonymous


using chrome ... everything is coming centered

04 Jun, 2013 - ccfontes

(def app (noir.util.middleware/app-handler app-routes))

Outputs this exception: Exception in thread "main" java.lang.IllegalArgumentException: Don't know how to create ISeq from: compojure.core$routes$fn__1394, compiling:(routes.clj:36:3)

I defined app-routes with "defroutes".

What am I doing wrong?

05 Jun, 2013 - ccfontes

Enclosing app-routes in [] solved it:

(def app (noir.util.middleware/app-handler [app-routes])) 

05 Jun, 2013 - anonymous

http://www.luminusweb.net/docs/profiles.md using chrome ... everything is coming centered --- Got it... markdown preview extension is installed in chrome ..so the docs was not properly displayed.

25 Jul, 2013 - ccfontes

With this request example:

{... :params {"foo.bar" "some value"}} 
Do you know why is that when using map keys containing ".", they aren't converted to keywords? The outcome should be:

{... :params {:foo.bar "some value"}} 
I tried explicitly:

:middleware [wrap-keyword-params] 
to no avail, but I suspect it's being used anyways, because keys without "." are always converted to keywords.

29 Jul, 2013 - Yogthos

It appears taht wrap-keyword-params has a restrictive syntax for what will be keywordized. Looks like you'd have to add your own middleware to keywordize keys with "." in them.

31 Jul, 2013 - yayitswei

When I try this:

(def app (middleware/app-handler all-routes))

I get the error

Caused by: java.lang.IllegalArgumentException: Don't know how to create ISeq from: compojure.core$routes$fn__4593

all-routes looks fine to me, what gives?

site.handler=> all-routes
#<core$routes$fn__4593 compojure.core$routes$fn__4593@35de06ec>


>quoted text
4 spaces indented code4 spaces indented code