Setting Up A Node.cljs Project With Leiningen and NPM

Before we begin, make sure you can start a node.cljs repl first

One important thing I want to highlight is that you should have a stable version of node 0.12.X as the linked wiki page says. Additionally, you want to make sure you have npm installed so you can install node packages later on. Finally, I'm also assuming that you already have Clojure and Clojurescript setup with Leiningen.

Let's begin.

Start by making a new leiningen project. 

lein new node_project

And then move into that directory.

Start by editing your project.clj file to look something like this:

(defproject node_test "0.1.0-SNAPSHOT"
  :dependencies [[org.clojure/clojure "1.7.0-RC1"]
                 [org.clojure/clojurescript "0.0-3269"]
                 [org.clojure/core.async "0.1.346.0-17112a-alpha"]]
  :profiles {:dev
             {:dependencies [[com.cemerick/piggieback "0.2.1"]
                             [org.clojure/tools.nrepl "0.2.10"]]
              :plugins [[lein-cljsbuild "1.0.5"]]
              :source-paths ["src" "dev"]
              :repl-options {:nrepl-middleware [cemerick.piggieback/wrap-cljs-repl]}}}
  :clean-targets
  [[:cljsbuild :builds 0 :compiler :output-to]
   :target-path
   :compile-path]
  :cljsbuild {:builds
              [{:id "dev"
                :source-paths ["src"]
                :compiler {:output-dir "out"
                           :output-to "index.js"
                           :optimizations :none
                           :source-map "src-maps"
                           :target :nodejs}}]})

It's very important that you use versions of Clojure and Clojurescript greater than or equal to the ones I've listed. I've ran into a lot of issues trying a setup like this before finding one that worked. It was mostly due to old versions of piggieback and Clojurescript.

I added the piggieback related dependencies in dev for the node.js REPL we're going to be using later. The clean-targets are for the command 

{% highlight sh %} lein clean ```

Because cljsbuild deprecated its clean function.

A few more things to note:

We're using no optimizations and targeting node.js. Having optimizations set to :simple, I ran into issues with core.async macros. 

Next, we need to setup the node side of the project. Inside the same project folder, run:

{% highlight sh %} npm init ```

You can pretty much press enter for every single prompt, except the entry point one. There you want it to be named app.js, not index.js as we specified in the project.clj. We can't run index.js directly due to optimizations being set to none, so cljsbuild will output multiple files.

Next, you can install a web framework using npm like express:

{% highlight sh %} npm install --save express ```

Now that we have Express.js installed, we can just make a simple hello world web application in clojurescript.

Rename src/node_test/core.clj to src/node_test/core.cljs.

Then in the file put this:

```clojure (ns node-test.core (:require [cljs.nodejs :as node]))

(def express (node/require “express”))

(def app (new express))

(defn -main [] (.listen app 3000 (fn [] (js/console.log “App Started at http://localhost:3000”))))

(set! main-cli-fn -main)

<p>Then you want to compile your clojurescript to javascript with:</p>

{% highlight sh %}
lein cljsbuild auto

Now that we have our index.js file, we need to make the file for node to run the application.

Make a file called app.js in the root of your project folder with this as its contents:

{% highlight js %}

// Bootstrap node.js require(’./out/goog/bootstrap/nodejs’);

// Our app compiled by cljsbuild require(’./index.js’);

// The core of our code require(’./out/node_test/core’);

// The core of cljs require(’./out/cljs/core’);

// Run main // NOTE: Dashes in namespaces are replaced by underscores. node_test.core._main();


<p>If you've changed anything, replace the paths with the correct one.</p>
<p>Now, you can start the app with node.js:</p>

{% highlight sh %}

$ node app.js
App Started at http://localhost:3000

And if you visit that url, you'll receive an error message about not being able to GET /. That's fine, we didn't specify any routes.


Join the 80/20 DevOps Newsletter

If you're an engineering leader or developer, you should subscribe to my 80/20 DevOps Newsletter. Give me 1 minute of your day, and I'll teach you essential DevOps skills. I cover topics like Kubernetes, AWS, Infrastructure as Code, and more.

Not sure yet? Check out the archive.

Unsubscribe at any time.