Search code examples
clojurecompojure

deploy compojure app that is also a CLI app


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.


Solution

  • 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.