ClojureScript + Firebase
Tags: clojure, functional, react
Published: 2020-12-31 09:39:54

# ClojureScript + Firebase

I'll talk about ClojureScript and Firebase. I use next libraries:

* shadow-cljs ... a build tool
* reagent ... React wrapper
* firebase npm library

## Setup a project

First of all, I generate a shadow-cljs project:

```bash
npx create-cljs-project cljs-firebase
```

Its results:

```bash
cd cljs-firebase
tree -a -I node_modules .
.
├── .gitignore
├── package-lock.json
├── package.json
├── shadow-cljs.edn
└── src
    ├── main
    └── test
```

Next, I edit a `shadow-cljs.edn`:

```clojure
;; shadow-cljs configuration
{:source-paths
 ["src/dev"
  "src/main"
  "src/test"]

 :dependencies
 [[binaryage/devtools "1.0.2"]
  [reagent "1.0.0"]]

 :builds
 {:app {:target :browser
        :output-dir "public/js"
        :asset-path "/js"

        :modules
        {:main
         {:entries [cljs-firebase.core]}}

        :devtools
        {:http-root "public"
         :http-port 8080
         :preloads [devtools.preload]}

        :release
        {:output-dir "dist/js"}}}}
```

And, I edit a `package.json`:

```json
{
  "name": "cljs-firebase",
  "version": "0.0.1",
  "private": true,
  "scripts": {
    "dev": "shadow-cljs watch app"
  },
  "devDependencies": {
    "shadow-cljs": "2.11.11"
  },
  "dependencies": {}
}
```

OK. I finished to set up a shadow-cljs, so I run the next command:

```bash
$ npm run dev

> cljs-firebase@0.0.1 dev /home/kbaba/repos/cljs-firebase
> shadow-cljs watch app

shadow-cljs - config: /home/kbaba/repos/cljs-firebase/shadow-cljs.edn
shadow-cljs - updating dependencies
Retrieving thheller/shadow-cljs/2.11.11/shadow-cljs-2.11.11.pom from https://repo.clojars.org/
Retrieving reagent/reagent/1.0.0/reagent-1.0.0.pom from https://repo.clojars.org/
Retrieving cljsjs/react/17.0.1-0/react-17.0.1-0.pom from https://repo.clojars.org/
Retrieving cljsjs/react-dom/17.0.1-0/react-dom-17.0.1-0.pom from https://repo.clojars.org/
Retrieving cljsjs/react-dom-server/17.0.1-0/react-dom-server-17.0.1-0.pom from https://repo.clojars.org/
Retrieving cljsjs/react-dom-server/17.0.1-0/react-dom-server-17.0.1-0.jar from https://repo.clojars.org/
Retrieving thheller/shadow-cljs/2.11.11/shadow-cljs-2.11.11-aot.jar from https://repo.clojars.org/
Retrieving reagent/reagent/1.0.0/reagent-1.0.0.jar from https://repo.clojars.org/Retrieving
 cljsjs/react-dom/17.0.1-0/react-dom-17.0.1-0.jar from https://repo.clojars.org/
Retrieving cljsjs/react/17.0.1-0/react-17.0.1-0.jar from https://repo.clojars.org/
shadow-cljs - dependencies updated
running: npm install --save --save-exact react@17.0.1 react-dom@17.0.1
+ react@17.0.1
+ react-dom@17.0.1
added 5 packages from 2 contributors and audited 104 packages in 2.176s

3 packages are looking for funding
  run `npm fund` for details

found 0 vulnerabilities

shadow-cljs - HTTP server available at http://localhost:8080
shadow-cljs - server version: 2.11.11 running at http://localhost:9630
shadow-cljs - nREPL server started on port 27730
shadow-cljs - watching build :app
[:app] Configuring build.
[:app] Compiling ...
[:app] Build failure:
The required namespace "cljs-firebase.core" is not available.
```

The command setups dependencies, builds the system and runs a server at `http://localhost:8080`. But, I didn't write any code, so the build was failure now. I will fix those errors.

I type `ctrl+c` to finish `npm run dev` and run the next commands:

```bash
$ mkdir src/main/cljs-firebase
$ touch src/main/cljs-firebase/core.cljs
```

I edit `src/main/cljs-firebase/core.cljs`:

```clojure
(ns cljs-firebase.core
  (:require [reagent.dom :as rdom]))

(defn view []
  [:div "hello world"])

(defn mount-root []
  (let [root-el (.getElementById js/document "app")]
    (rdom/unmount-component-at-node root-el)
    (rdom/render view root-el)))

(defn ^:export init []
  (mount-root))
```

I rerun `npm run dev`

```
$ npm run dev

> cljs-firebase@0.0.1 dev /home/kbaba/repos/cljs-firebase
> shadow-cljs watch app

shadow-cljs - config: /home/kbaba/repos/cljs-firebase/shadow-cljs.edn
shadow-cljs - HTTP server available at http://localhost:8080
shadow-cljs - server version: 2.11.11 running at http://localhost:9630
shadow-cljs - nREPL server started on port 38281
shadow-cljs - watching build :app
[:app] Configuring build.
[:app] Compiling ...
[:app] Build completed. (173 files, 0 compiled, 0 warnings, 7.39s)
```

And, I open a browser and access `http://localhost:8080`

![localhost:8080](https://dev-to-uploads.s3.amazonaws.com/i/bvo1vrvcj5m4pc1p5cda.png)

## Initialize Firebase

I install `firebase`

```
npm install --save firebase
```

And, I create a next file:

```clojure
; src/main/cljs_firebase/firebase/init.cljs
(ns cljs-firebase.firebase.init
  (:require ["@firebase/app" :refer (firebase)]))

(defn initialize-firebase []
  (if (zero? (count (.-apps firebase)))
    (-> firebase
        (.initializeApp
         #js {:apiKey "..."
              :authDomain "..."
              :databaseURL "..."
              :projectId "..."
              :storageBucket "..."
              :messagingSenderId "..."
              :appId "..."}))
    (.-app firebase)))
```

We can use npm packages by using `""` at `:require`, so I wrote `["@firebase/app" :refer (firebase)]`. And, I initialize Firebase by config in Firebase SDK snippet.

I edit `src/main/cljs_firebase/core.cljs`:

```diff
 (ns cljs-firebase.core
-  (:require [reagent.dom :as rdom]))
+  (:require [reagent.dom :as rdom]
+            [cljs-firebase.firebase.init :refer [initialize-firebase]]))

 (defn view []
   [:div "hello world"])
@@ -10,4 +11,5 @@
     (rdom/render view root-el)))

 (defn ^:export init []
+  (initialize-firebase)
   (mount-root))
```

Next, I will connect to Firestore.

## Connect to Firestore

I make a next file:

```clojure
; src/main/word_penne/firebase/firestore.cljs
(ns cljs-firebase.firebase.firestore
  (:require ["@firebase/app" :refer (firebase)]
            ["@firebase/firestore"]))

(defn firestore []
  (.firestore firebase))
```

And I edit `src/main/cljs_firebase/core.cljs`

```clojure
(ns cljs-firebase.core
  (:require [reagent.dom :as rdom]
            [reagent.core :as r]
            [cljs-firebase.firebase.init :refer [initialize-firebase]]
            [cljs-firebase.firebase.firestore :refer [firestore]]))

(def todos (r/atom [{:task "aaa"}]))

(defn set-todos [val]
  (reset! todos val))

(defn load-todos-from-firestore []
  (-> (firestore)
      (.collection "todo")
      (.get)
      (.then
       (fn [snapshot]
         (let [result (r/atom [])]
           (.forEach snapshot
                     (fn [doc]
                       (swap! result conj
                              (conj {:uid (.-id doc)}
                                    (js->clj (.data doc) :keywordize-keys true)))))
           (set-todos @result))))))

(defn view []
  [:div "hello world"
   [:ul
    (js/console.log @todos)
    (for [todo @todos]
      [:li (:task todo)])]])

(defn mount-root []
  (let [root-el (.getElementById js/document "app")]
    (rdom/unmount-component-at-node root-el)
    (rdom/render view root-el)))

(defn ^:export init []
  (initialize-firebase)
  (load-todos-from-firestore)
  (mount-root))
```

## conclusions

I connected to Firebase and got data. Because I used npm packages directly, the system is easy to maintain. And, we can use Firebase Authentication, Functions, etc.
I made a system by using those and Reagent, re-frame, stylefy, etc.