Any example project setups for shadow-cljs + clj backend using tools.deps?

Hi everyone,

This is the first topic that I create here on Clojureverse. I’ve been a member a little while, but have so far only been reading and picking up programming techniques and and tooling details, which is great for a beginner in this universe where the learning curve is quite steep.

One thing I haven’t seen so far, though, and haven’t quite figured out how to do myself, is how do you people create a web project with a ClojureScript frontend using shadow-cljs and a regular Clojure backend that serves the frontend, using tools.deps for the backend.

So:

  • How do you structure the project - two projects as a monorepo/mixed repo/…?
  • How do you serve the frontend from the backend?
  • How do you build everything into an uberjar in one go?

And as a bonus:
Do you have this working in Emacs? With a clj repl for the backend and a cljs repl for the frontend at the same time?

Thanks for any replies.

3 Likes

I recently did something like this myself to learn about re-frame. I used separate repos, but may reconsider using a mono-repo in the future. I use Emacs with cider. shadow-cljs and deps.edn on the client side and deps.edn on the server side. I have not done any deployment and do not expect to use it in production, so no answer to your uberjar question. Take a look at

https://github.com/bombaywalla/hix (frontend)
https://github.com/bombaywalla/hax (backend)

2 Likes

Thanks! These really nice examples. I like the helpful READMEs and that you have covered testing as well which is a nice bonus for me :slight_smile:

I created a small template for my own use with this exact setup, mixing clj and cljs in the same code base: https://github.com/mdiin/happlate.

The readme is still very much incomplete, and only covers running through atom with the chlorine plugin. At this time I am using Cursive for developing my app, which works even more smoothly than atom+chlorine due to REPL commands and integrated clojure.main repl. The concepts of my atom setup are still valid though, and I consider them important steps on my road to my current setup.

I had some trouble getting a satisfactory setup for combined Clojure and Clojurescript projects with emacs+cider.

2 Likes

This is awesome! Thanks so much for posting this, this is incredibly helpful.

2 Likes

We use a monorepo for each product that we build (https://storrito.com/ and https://audiocado.com/). Lately we also use Git LFS to store all marketing, design and other files into this repo.

Our frontend and backend are tightly coupled, meaning the backend API is designed exactly for this frontend app, which I consider to be a good trade-off, if you are working in a small developer team: https://maxweber.github.io/blog/2019-07-25-introducing-db-view-part-1

After some initial hesitation we are using Kubernetes or rather Google Cloud’s Kubernetes Engine to operate our new service Audiocado. I learned the hard way that in this context the Docker container should be your “uberjar”. Google released a Java tool jib to directly build container images, without the need to run a Docker daemon. However the important thing is that this saves you a lot of headaches regarding the caching of your dependencies, since the build process can use your local ~/.m2/repository cache, while a build running inside a Docker container starts to download everything from Maven Central and Clojars. Luckily there is already a tool that uses jib in combination with tools.deps: https://github.com/juxt/pack.alpha#docker-image We also build the frontend with shadow-cljs outside of the Docker container, to use the existing node_modules folder and .shadow-cljs cache. I know that is not the best practice for “continuous integration / delivery” :sweat_smile: But guess what you build an entire product even without having a “continuous integration server”.

Yes, I work in Emacs with the CLJ and the CLJS repl at the same time. However if you are new to Emacs, I would rather skip this and start with https://cursive-ide.com/

In general I’m missing a project template, that would help to build a full product with Clojure and ClojureScript. There are a lot of them (free and paid ones) for other programming languages (a paid example: https://usegravity.app/). Otherwise you spend weeks with numerous of difficult technical decisions / trade-offs before you can delight your customers with the first feature.

2 Likes

Thank you! There are tons of valuable information in your reply. I really appreciate your blog posts about db-view. I’ve been thinking about something like this for quite a while after reading a blog post by Magnar Sveen (which I cannot find atm) talking about a similar but different concept of pushing incremental updates, or facts, to the frontend over websockets while using Datascript for the frontend state.

This is really inspiring, and I can’t wait to try out this concept.

It is good to hear that you have a successful setup with Emacs. I’m relatively new to Emacs, and a long time IntelliJ user, so it would only be natural to use Cursive and enjoy the productivity it would give me. I am however hooked by Emacs for reasons that I cannot explain :smile:

Now with both yours and @mdiin’s excellent insights I will have a lot to go on to get a satisfactory project setup, and a new client/server architecture to boot :slight_smile:

Edit: jib looks promising. Will definitively have a closer look. We’re using Docker at work (no Clojure, though, at least not yet) and will start using Kubernetes in the not so far future. So this knowledge will come in handy.

Happy you are able to use my template for something useful. :smiley:

If you decide to go with Cursive and get stuck on the setup, I will be happy to try and provide some more details on how I got it working. I do get the lure of Emacs though, as I am myself a long-time Emacser. :slight_smile:

1 Like

I tried exactly this: https://maxweber.github.io/blog/2019-06-04-approaching-the-web-after-tomorrow

… and I failed :slightly_smiling_face: Each customer only received the relevant datoms for his account at the initial page load. However some customers had hundred thousands of datoms and they needed to wait up to 20 seconds until the app was ready. Building the in-memory Datascript indexes also becomes very slow for this amount of datoms. Furthermore it blocks the JS main thread and makes the app unresponsive for a few seconds.

Glad to hear that you like the db-view approach. I also have to update the example app, since I discovered a problem with the notify mechanism recently. The initial version used HTTP long polling, where it is possible that you miss a server push message between two requests. Audiocado now uses server-sent-events instead. A long time ago we used web sockets for Storrito, but it astonishing difficult to implement a robust reconnection mechanism for it (took us around 1 week).

Thanks for sharing! These kind of experiences are nice to read about instead of learning the hard way. Although, I guess you have learnt a lot in the process :slight_smile:

I have a full stack app with a CLJ + Datomic backend and a CLJS admin frontend via Shadow-CLJS and tools.deps, all in a mono repo:

(FYI it won’t run without a copy of the database. If you want to actually run it let me know.)

The admin UI is served from a pedestal server, and talks to the backend via a Pathom EQL API endpoint generated from a domain model. The public-facing UI is in VueJS (separate repo) and talks to a GraphQL endpoint.

I use IntelliJ and Cursive and usually have several REPLs open, one CLJ backend and one CLJS frontend. I run the app in a virtual machine via the Clojure CLI which includes a Socket REPL server, so I can open a REPL connection from my IDE through a forwarded SSH port to connect directly to the running production app in case something needs to be done more quickly than pushing changes via Git and rebooting the app.

1 Like