(iterate think (think))

Luminus: a web framework for Clojure

24 Dec, 2012

Since the retirement of Noir, there aren't any batteries included web frameworks for Clojure. As I mentioned in an earlier post, moving to Compojure is fairly painless. However, you still have to put a lot of things together by hand.

I suspect this isn't a problem for most people who've already been doing Clojure development. However, it can be daunting for beginners and it also means having to write a lot of boiler plate when making a new site.

I decided to see if I could tie some common libraries together to provide a more comprehensive solution for creating web applications in Clojure. This led to the creation of the Luminus framework, which follows in footsteps of Noir in attempting to make web development in Clojure an easy and accessible experience.

The framework consists of two parts, first is the lib-luminus, which provides some useful utility functions, which I found to be helpful when writing applications. The second is the luminus-template, which is used to generate the base application.

The resulting app is ready to be run standalone or deployed as a war. It can also be run on Heroku by following the steps in the official documentation.

The application generated by the template can be easily modified to fit your needs and shouldn't be any more restrictive than a standard Compojure app. This avoids some of the issues with Noir, where things like using custom middleware were problematic.

The documentation site for Luminus is built using the framework in the spirit of eating my own dog food, and its source is available on github as well.

Hopefully this will be useful in helping people get started. I intend to continue working on it and I'm always open to suggestions, patches, and collaboration. :)



tags clojureluminus

comments


24 Dec, 2012 - Ed Tsech

Thanks Dmirti for your work. I understood your motivation, but also would like to hear about your feature plans. Now luminus is really similar to Noir. Is't main idea or you have plans for additional functionality? What do you think about adding sensable defaults for logging, auth, tests, database migrations, data validation, environment managment ? Noir's defpage macro had ability to create named routes, do you think about implement smth like that for luminus on the top of compojure dsl?

24 Dec, 2012 - xhh

It's cool! I'm starting to write a website in Clojure.

I have read from the new maintainer of lib-noir that it would be as easy using compojure with lib-noir as using Noir, so I'm wondering what's the difference by using Luminus. I'll try your lib-luminus myself though. Thanks for your work!

24 Dec, 2012 - Yogthos

@Ed Tsech

I'm definitely planning on adding more functionality as I go along. Unlike Noir, I'd like to keep the Compojure routing exposed as is. The defpage macro added some complications and in my opinion didn't add too much value on top of the way Compojure routes are defined. For example, with routes it's much easier to tell where all the entry points to the application are.

Adding sensible defaults via template, and helpers in the library keeps it flexible, and allows people to easily modify the template project if it's not working the way they want.

24 Dec, 2012 - Yogthos

@xhh

Luminus is a helper library with a project template and it uses lib-noir as one of the dependencies. If you use Luminus you'll have a batteries included Compojure application to start with. If you later find you don't need any functionality from lib-luminus, you can just remove it from dependencies.

24 Dec, 2012 - Ed Tsech

I absolutely agree with you about complications of defpage macro, it was one of reasons why I didn't use Noir. Just named routes looks like useful pattern, probably it can be implemented in different way, without loosing flexibility.

24 Dec, 2012 - Yogthos

I'm of two minds on that one, on the one hand it makes defining controller and view very clean, but on the other it makes it more difficult to tell where the entry points are. There's definitely some more low hanging fruit to pick before looking into named routes I think. :P

04 Jan, 2013 - Dave

Hi just tried luminus on Windows 7 and got a big fat "String Index Out of Range". I've tried the same code on Linux and everything works fine. Pretty new to this so not sure where to raise a ticket.

06 Jan, 2013 - Yogthos

@Dave

I don't have a windows machine around, but if you could give a bit more details as to what you were doing I might be able to help. :)

11 Jan, 2013 - tomP

I noticed that Luminus now uses lein-ring.

Question: How do I upgrade an existing luminus project to use the newest luminus framework?

Thanks!

13 Jan, 2013 - Yogthos

@tomP

It's actually pretty straight forward. First, you have to update your project.clj, where you make the following changes.

  • in dependencies, replace [ring/ring-jetty-adapter "1.1.0"] with [ring-server "0.2.5"]
  • make sure lein-ring plugin is at version 0.8.0: :plugins [[lein-ring "0.8.0"]]
  • remve the :main <yourapp>.server, since launching is now taken care of by ring
  • add production section to the :profiles


:profiles
  {:production 
   {:ring
    {:open-browser? false, :stacktraces? false, :auto-reload? false}}
   :dev 
   {:dependencies [[ring-mock "0.1.3"] [ring/ring-devel "1.1.0"]]}} 

then replace your server.clj with repl.clj which looks as follows:


(ns myapp.repl    
  (:use myapp.handler
        ring.server.standalone
        [ring.middleware file-info file]))

(defonce server (atom nil))

(defn get-handler []
  (-> #'app
    (wrap-file "resources")
    (wrap-file-info)))

(defn start-server
  "used for starting the server in development mode from REPL"
  [& [port]]
  (let [port (if port (Integer/parseInt port) 8080)]
    (reset! server
            (serve (get-handler) 
                   {:port port 
                    :init init
                    :auto-reload? true
                    :destroy destroy 
                    :join true}))
    (println "Server started on port [" port "].")
    (println (str "You can view the site at http://localhost:" port))))

(defn stop-server []
  (.stop @server)
  (reset! server nil))

You might also need to add the destroy function to your handler, or get rid of the :destroy flag in the serve call if you're not using it.

Finally, if you're deploying to Heroku, change your Procfile to this:


web: lein with-profile production ring server

That should be all the changes you need to get up to date with the new template.

Running the application in development mode can now be done either from the REPL using the start-server/stop-server functions from above or using lein with lein ring server.

Packaging standalone is now done by running lein ring uberjar as opposed to lein uberjar.

03 Feb, 2013 - Alex

I have a little question... I am creating a project with Leiningen, using the


lein new luminus guestbook

command, as it is advised in the tutorial. However, the created project has no mention of Luminus! The project.clj contains only this:


(defproject luminus "1.0.0-SNAPSHOT"
  :description "FIXME: write description"
  :dependencies [[org.clojure/clojure "1.3.0"]])

So why no Luminus and why Clojure 1.3.0??? I am new to Clojure so please bear with me..

06 Feb, 2013 - Yogthos

@Alex

From the looks of your project.clj it appears that the template wasn't triggered by Leiningen. Instead of invoking the luminus template to create the project, it created a project using the default template and called it luminus. The guestbook argument was simply ignored.

It's likely you're running Leiningen 1.x without the lein-newnew plugin. The template only supports version 2.x, so you probably need to follow the instructions here to upgrade it. You can check your version by running:


lein version

When you create a new project you should see this message if everything works correctly:


lein new luminus guestbook
Generating a lovely new Luminus project named guestbook...

The project.clj in the generated project should also look something like this:


(defproject guestbook "0.1.0-SNAPSHOT"
  :description "FIXME: write description"
  :url "http://example.com/FIXME"
  :dependencies [[org.clojure/clojure "1.4.0"]
                 [lib-noir "0.3.5"]
                 [compojure "1.1.5"]
                 [hiccup "1.0.2"]
                 [ring-server "0.2.7"]
                 [com.taoensso/timbre "1.2.0"]
                 [com.taoensso/tower "1.2.0"]
                 [markdown-clj "0.9.19"]]
  :plugins [[lein-ring "0.8.2"]]
  :ring {:handler guestbook.handler/war-handler
         :init    guestbook.handler/init
         :destroy guestbook.handler/destroy}
  :profiles
  {:production {:ring {:open-browser? false
                       :stacktraces?  false
                       :auto-reload?  false}}
   :dev {:dependencies [[ring-mock "0.1.3"]
                        [ring/ring-devel "1.1.8"]]}}
  :min-lein-version "2.0.0")

10 Feb, 2013 - Alex

Oh! Really, it was some ancient Leiningen 1.7.1 installation that was referenced in the PATH system variable before the 2.0.0 that I have installed to explore Luminus. After I have cleaned that stuff up, everything went as you said.

I have another question though: currently I am using Scala as my main programming language, but am interested in Clojure, especially for web development. On Scala I use PlayFramework 2.1, which is a really nice web framework, and it is fully asynchronous and has an asynchronous plugin for interfacing with MongoDB, called ReactiveMongo. Since I am totally new to Clojure, I would like to ask you, is Luminus fully asynchronous and does Clojure have similar asynchronous drivers for working with MongoDB?

For now, one thing that I have noticed is that Luminus is using Jetty, which is a thread-breeder, instead of Netty, which is based on NIO.. ?

12 Feb, 2013 - Yogthos

Hi Alex,

Glad you got the build problems solved. As for your questions, each request to a route is handled asynchronously. Since routes are handled as regular servlet requests, each one runs as an independent thread.

There are a couple of popular libraries for interfacing with MongoDB, one is congomongo and the other is monger. However, I haven't used MonogDB myself so I can't tell you whether they're asynchronous or not.

Jetty is just the embedded server which is used by default when you create a standalone jar. If you create a war using lein ring uberwar it can be deployed on any app server such as Tomcat, Glassfish, or JBoss. etc. Using threads is fairly standard among the JVM app servers though. In my experience it's hasn't been a performance issue because of thread pooling.

You might also want to look at http-kit, which is a drop in replacement for the Jetty adapter. Apparently, it gets very good throughput. :)

If you're interested in working with Netty specifically, I recommend taking a look at Aleph.

15 Feb, 2013 - Alex

Thank you very much! I will explore Luminus and everything else you have suggested! I think that Clojure is great and can't wait to get into it)

13 Apr, 2013 - Wei

Is there a good way of using http-kit with your suggested deployment method of "lein ring server"?

17 Apr, 2013 - Yogthos

@Wei

I haven't played with http-kit much myself. I think you'd have to write your own main as shown on the http-kit migration page. Then you'd use leing run to run and lein uberjar to package it.




help

*italics*italics
**bold**bold
~~foo~~strikethrough
[link](http://http://example.net/)link
super^scriptsuperscript
>quoted text
4 spaces indented code4 spaces indented code

preview

submit