I have a Clojure app that I can use both from the command-line, and as a Compojure app.
I did that by putting a ring handler and a main function (clojure.tools.cli
) in my leiningen project.clj
.
{...
:main my-app.core
:ring {:handler my-app.handler/handler }}
The handler
is defined (defroutes handler ...)
.
Now if I want to run the CLI app, I can run lein uberjar
and then java -jar arguments
.
And I can also run the Compojure app via lein ring server PORT
.
Now, how do I deploy the thing as a Compojure app (and not a CLI app) in a production server ? (Bonus points for explaining how lein ring server
works.)
Note : I already use nginx if that can help, and I'm flexible on the container to be used.
Here's kind of the default template I use for new projects. It allows you to do dependency injection into ring apps and run the app from the command line as an uberjar. You can read more here: http://www.sparxeng.com/blog/software/improved-clojure-workflow
; handler.clj
(defn wrap-inject-deps
"Ring middleware that injects the dependencies into each ring request map"
[handler deps]
(fn [req]
(handler (assoc req :deps deps))))
(defn create-handler
"Similar to the default ring 'handler', but with a parameter to let you inject dependencies"
[deps]
(-> (routes
api-routes
web-routes
(route/resources "/"))
(kwp/wrap-keyword-params)
(params/wrap-params)
(json/wrap-json-params)
(json/wrap-json-response)
(wrap-inject-deps deps))) ; this injects dependencies
(defn start-jetty
"Launch the Jetty instance"
[deps]
(let [port (Integer. (or (-> system :env :port) 5000))
handler (create-handler deps)]
(jetty/run-jetty handler {:port port :join? false})))
; system.clj
(defn get-env
"Initialize configuration vars"
[]
{:aws-access-key-id (System/getenv "AWS_ACCESS_KEY_ID")
:aws-secret-access-key (System/getenv "AWS_SECRET_ACCESS_KEY")
:mongo-url (System/getenv "MONGO_URL"))
(defn start
"Launch dependencies such as DB connection and Jetty. Return these as a map, for use in REPL"
[& [env]]
(let [env (or env (get-env))
deps {:env env
:monger (db/init env)}
jetty (handler/start-jetty deps)]
(assoc deps :jetty jetty)))
; program.clj
(defn -main [& [port]]
"App entrypoint"
(let [env (system/get-env) ; "env" is just a map of config variables, that can be hard-coded, read from file, or whatever you want.
env (if port (assoc env :port port) env)]
(system/start env)))
You can then use leiningen profiles if you need to create multiple apps with different entrypoints from your codebase.