I’m no longer maintaining Frodo (not that it needed much maintenance anyway!) because the Clojure-world has moved on. I’d recommend checking out Stuart Sierra’s Component library, or my own Yo-yo library.
A Leiningen plugin to start a web server (backed by http-kit) easily via configuration in Nomad.
- Easy setup of a web server that you can nREPL into
- Easy integration with Stuart Sierra’s (@stuartsierra) ‘Reloaded’ workflow
- Ability to open up an nREPL even if you have compiler errors - you can use the REPL to find them without restarting the JVM each time
Include lein-frodo
as a plugin in your project.clj
:
:plugins [[jarohen/lein-frodo "0.4.2"]]
Frodo versions earlier than 0.4.1 are not compatible with Nomad 0.7.0 or later, please update your Frodo version.
Now moved to CHANGES.org.
Well, I already use Nomad for most of my configuration. I configure various environments using Nomad’s environment functionality, and wanted the web server to be configured in the same way.
In each project, I found myself writing the same boilerplate startup code - reading the port details from my configuration in order to start nREPL and a web server on the relevant ports for the environment.
With Frodo, it’s possible to start web applications with:
NOMAD_ENV=<<environment>> lein frodo
and have the ports vary by environment.
For more details about what’s possible with Nomad, please see its project page.
(I did use lein-ring for a bit but, while it is a great plugin, I’d much prefer all my configuration in one place - hence taking the time to write this!)
Yes, it’s corny, I’m sorry! I did toy with lein-nomad-ring, and various permutations, but none of them really seemed to bring together Ring and Nomad in the way lein-frodo did. Alternatives gratefully received!
The easiest way to start a Frodo project is by using the SPLAT Lein template - most, if not all, of the below is done for you.
First, create a Nomad configuration file somewhere on your classpath,
and add a :frodo/config
key, as follows:
;; *project-root*/resources/config/nomad-config.edn:
{:frodo/config {:nrepl {:port 7888}
:web {:port 3000
:app myapp.web/app}}}
Frodo expects apps to provide an instance of its App
protocol,
containing two functions: start!
and stop!
.
(ns myapp.web
(:require [frodo.web :refer [App]]
[compojure.core :refer [routes GET]]
[ring.util.response :refer [response]]))
(defn api-routes [db-conn]
(routes
(GET "/items" [] (response (get-items db-conn)))))
;; recommended as of 0.3.0
(def app
(reify App
(start! [_]
(let [db-conn (connect-db! ...)]
{:db-conn db-conn
:scheduler (start-scheduler! db-conn)
;; Apps must return a :frodo/handler key
:frodo/handler (api-routes db-conn)}))
(stop! [_ system]
(stop-scheduler! (:scheduler system))
(disconnect-db! (:db-conn system)))))
Frodo expects the start!
function to return a system map containing
at least a :frodo/handler
key. When the server is stopped or
restarted, this system map is passed to the stop!
function for you
to tear down any state/close resources etc (in line with the
‘Reloaded’ workflow - more details below).
When you’ve created your App
, add an entry in your project.clj
to
tell Frodo where your config file is:
:frodo/config-resource "config/nomad-config.edn"
To run the Ring server, run:
lein frodo
Frodo supports two alternatives to providing an ‘app’, for backwards
compatibility: :handler-fn
and :handler
. ‘Handler functions’ are
0-arg functions that return a Ring handler, whereas the simpler
‘handler’ is a static handler.
These may be removed in a future version of Frodo.
;; ---- config/nomad-config.edn ----
{:frodo/config {:nrepl {:port 7888}
:web {:port 3000
;; any one of :app, :handler-fn or :handler is req'd
:app myapp.web/app
:handler-fn myapp.web/make-handler
:handler myapp.web/handler}}}
;; ---- myapp/web.clj ----
;; like the 'start!' function of 'app' - no corresponding 'stop!' fn
;; though.
(defn make-handler []
(let [db-conn (connect-db! ...)]
(api-routes db-conn)))
;; static handler
(def handler
(routes
(GET "/" [] (response "Hello world!"))))
Yes - you can do this in the traditional Nomad way:
;; *project-root*/resources/config/nomad-config.edn:
{:nomad/environments {"dev"
{:frodo/config {:nrepl {:port 7888}
:web {:port 3000}}}
"prod"
{:frodo/config {:nrepl {:port nil}
:web {:port 4462}}}}}
Then, start your application with either:
NOMAD_ENV=dev lein frodo
or:
NOMAD_ENV=prod lein frodo
This is just the simplest multiple environment configuration - there are many more possibilities on the Nomad project page.
You can pass options to HTTP-kit by specifying a :http-kit/options
key in the :web
map:
{:frodo/config {:nrepl {...}
:web {:port ...
:handler-fn ...
:http-kit/options {:thread 100}}}}
For a full list of the options that HTTP-kit accepts, please see here.
As of 0.2.6, you can develop web-apps in Frodo using Stuart Sierra’s ‘Reloaded’ workflow. I won’t go into huge detail about the pattern itself (his blog is very informative and plenty else has been written about the benefits!) but I do find it a great way to get a ‘fresh’ state without having to restart the JVM.
Essentially:
- Set up your system state and resources in the
start!
function (for anApp
). - Ensure that your code doesn’t contain any
def
’s ordefonce
’s (and preferably nodefroutes
’s - replace these with(defn my-routes [] (routes ...))
) so that all the state can be reloaded. - Tear down any state and close resources in the
stop!
function - Call
(reload-frodo!)
from theuser
namespace to throw out the old state and start afresh. This will stop the web server, refresh any changed code files, and restart the web server, without restarting the JVM. This typically takes less than a second. - Call
(frodo-instance)
to get access to the currently running instance.
To restart the web server from your REPL:
user> (reload-frodo!)
;; Stopping web server.
;; :reloading (tetris.multiplayer tetris.handler)
;; Starting web server, port 3000
;; => nil
To build a batteries-included JAR file of your application, run lein
frodo uberjar
.
- SSL? I’m not sure how many people use SSL within Clojure - from what I can tell most people sit it behind an nginx/httpd proxy. If you want to include SSL support, please feel free to submit a pull request.
- uberwar? Again, I don’t use this, but if you do and you care enough to write a patch, it’ll be gratefully received!
Yes please, much appreciated! Please submit via GitHub in the traditional manner. (Or, if it fits into 140 chars, you can tweet @jarohen)
- Big thanks to James Reeves for his lein-ring project (amongst everything else!) from which I have plundered a couple of ideas and snippets of code. Also, thanks for the general help and advice.
- Thanks to Stuart Sierra for writing up his ’Reloaded’ workflow - a great way of thinking about server-side state in Clojure
Copyright © 2013, 2014 James Henderson
Distributed under the Eclipse Public License, the same as Clojure.