A Beginner's Journey Through the Clojure Web Development Landscape

I mostly do Clojure for fun, and I've only recently started developing small Clojure projects at work. My journey began with hobby projects, as I worked on mastering functional programming, configuring my IDE, and learning to write and think in Clojure.

The first time I tried ClojureScript four years ago I was naive enough to ignore JavaScript and the DOM (Document Object Model) — as someone with a background in the Social Sciences, I had to learn many concepts that Computer Scientists take for granted. That was my first lesson: no matter the language, if you want to do web development you have to at least master the DOM, HTML and the JavaScript API that controls it. From here on you'll find it much easier to pick up things like CSS and whatever framework you choose.

If you're thinking this is a lot to learn, you're right—it is, if you try to do it all at once. Learning multiple skills at the same time is tough, particularly when you're learning in your spare time. It's much easier to take it step by step.

You may also wonder: “if up to this point I have learned JavaScript, HTML and CSS, why should I learn ClojureScript? I should learn Vue, React or Angular instead”. You should definitely learn some React. It is heavily used in the ClojureScript ecosystem. And yes, you don’t need to learn ClojureScript to do web development. You can read many things about the advantages of functional programming, but what motivated me to learn Clojure and ClojureScript was the simple fact that I saw it and I liked it. And this suffices: I love this and I want to build stuff with this. There are of course many good reasons for learning Clojure and ClojureScript and there are many articles and videos exposing the pros and cons of this wonderful language, however there is no rational justification for tastes. It just sticks and that’s it.

So, if you tried JavaScript or something else and it just didn't work for you, and you're starting to think programming isn't your thing, hold on! There are tons of programming languages out there, and a lot of them can do frontend. Even if you're happy with what you know, I'd still recommend trying something new, especially a language from a different programming paradigm. I started with Java, but Clojure is where I landed. I love it because the syntax is super clean, functional programs are easier to understand, and the REPL workflow is just incredibly interactive—almost addictive.

A couple of years ago, I decided I wanted to dive deep into Clojure Web Development and write about what I learned. I was definitely too ambitious. Time has just flown by, and I haven't gotten to everything I wanted to, and I wouldn't say I've mastered anything yet. Plus, this article isn't nearly as structured as I originally planned. I thought I'd write some kind of complete guide to Clojure Web Dev, but that turned out to be way too big of a project. I just don't have the expertise or the time. So, I figured I'd just write down what I've learned so far. I hope my experiences can help others who are learning Clojure and ClojureScript.

Where to start?

If you're just starting out, you're probably asking yourself, 'Where do I begin?' And you might be surprised to hear, 'Well, it depends.' But that's the truth. Your starting point really depends on what you want to do. So, before anything else, figure out your goal. Then, you can start looking at which Clojure tools will help you get there.

Therefore, the first thing we shall do is to differentiate the distinct domains of this wild jungle called web development. Without getting too technical, web projects generally fall into these categories:

  1. Static Sites: These are websites where the content doesn't change unless you modify and redeploy the site. Think of blogs, documentation sites, or company landing pages. Modern static sites can still be interactive through JavaScript but their core content is pre-built.

  2. Web Applications: These come in several flavors:

    • Server-Rendered Applications: The server creates HTML pages on demand in response to the requests of the user. Each page load typically results in a fresh request to the server.

    • Dynamic Applications: This is like the classic web application where the backend retrieves information from the database in response to requests from the client, while the frontend handles the interaction with the user and displays the information received from the backend. However, there is also the possibility to launch just a web client app that consumes information from one or many public sources.

    • Single Page Applications (SPAs): The browser loads a minimal HTML skeleton, and then our program takes over to build and update the interface. Most interactions happen without full page reloads.

    • Hybrid Applications: Modern web development often combines these approaches, taking advantage of server-rendering for initial fast loading and SEO, while using client-side rendering for dynamic interactions.

    • Progressive Web Apps: What defines a PWA is that they are: a) instalable; b) able to run offline and/or in the background and; c) integrate with your OS and/or App Store. For more details I recommend you to visit this site.

I will be covering here static sites and the first three types of web applications, namely, server-rendered applications, dynamic applications and SPAs.

The choice between these approaches depends on your specific needs, which basically boils down to where do you want to place the computing load, namely, at the backend, at the frontend or reaching some compromise between both. Do you need to persist data? If not, you might just need a simple web client. How much user interaction are you willing to handle? For instance, a blog or a plain landing site might work perfectly as a static site, while a complex dashboard would benefit from being a SPA.

Understanding these distinctions will help you choose the right tools from Clojure's ecosystem for your project.

Static sites

A common use case are blogs and Clojure has some attractive options. The one I tried is called Cryogen. The nice thing about this library is that there is almost no coding, you just have to write your entries as Markdown files in the right folders, content/md/pages or content/md/post depending on what you want to do. You can also use AsciiDoc if you prefer.

Regarding style there are some templates you can choose from or else you can just edit the CSS yourself. And when the time to deploy comes, there are some very nice options, especially the free ones, such as GitHub Pages and Netlify.

If you are deploying to GithubPages there is the option to configure a Github Action to automate the deployment of your site. This option is thoroughly explained in this post by John Stevenson (practicalli). There is nothing much to add. It’s really easy.

There are some options I would like to try later. The first one is Borkdude’s Quickblog and then there is Christian Johannsen’s Powerpack. The latter looks very mature and full-batteries, while the former has the leverage of Babashka.

Web Applications

Server-Rendered Applications

One of the simplest ways of building a web app is to handle everything from the backend. You need a web server to serve your static files, such as HTML, CSS and any other asset you might need such as images or icons. Then you need to choose a database engine and define your data layer; typically, in Clojure you will choose a library that interfaces with the Driver of the chosen database engine using the JDBC protocol, and optionally another library to handle SQL. And finally you have to decide how to render HTML and pick up a templating library to add dynamism to you page.

I built a simple website with the purpose to document a system we use at work. I spinned up a Jetty server using the Ring library to handle HTTP, chose PostgreSQL as a DB engine (so I added the correspoding driver to my deps.edn file) and used next-jdbc and Hikari-CP to create a connection pool. Regarding SQL I picked up HoneySQL which is really easy to use and well-documented. Later, in another unrelated project, I also tried HugSQL. It’s really good as well. Only a few projects later I understood that you should prefer HoneySQL if you will need to create SQL programmatically, in any other case, HugSQL might be the best choice.

In order to render HTML I used the popular Hiccup library. Hiccup makes it truly easy to write HTML. Believe me! You don’t want to write HTML as strings! There are also alternatives heavily inspired by it. One of them is Borkdude’s html, having the advantage of being compatible with both Clojure and ClojureScript, and there is also the fast Chassis that offers the choice to compile your HTML. It would be nice to try them out another time.

For template rendering, I chose Enlive. Yeah, it's an older library, but it's super stable. It might not be the trendiest choice right now, but it let me think of my pages as a bunch of separate components. You start with a template that's just a regular HTML file, and then you define snippets for different parts of the page. Using selectors á la CSS allow you to smoothly traverse the HTML tree. Another option you might see a lot is Selmer.

My experience using these technologies was very nice and smooth. You can go really fast with these and since my page was pretty simple so was my setup, so I did not need any library to manage state.

Dynamic Applications

I'll begin with the simplest approaches. But, to be honest, this isn't how I learned. I really wish it was! In fact, it's only in the last few months that I've been inspired by talks and articles to explore Vanilla JavaScript.

The first time I became aware of the problem was by reading this blog post by Niki Tonksy, where he just visited several popular websites and realized how much data they loaded. It was a lot most of the time. But why? Is it necessary for these applications to be so big? — he asked himself. He made an experiment (https://allekinos.de/) some time thereafter showing that that wasn’t the case. Then I found this post in Thomas Heller’s blog where he showed how you can do by yourself what HTMX does without being limited by what the library offered. So I started to dig into Web Components, Semantic HTML and so on (if you want a screening on Web Components, I found this talk of Maximiliano Firtman useful and if you want to explore how far can you go with just HTML watch this talk of Amy Kapernick). More recently I also found out about this no build movement led by David Heinemeier Hansson (by the way, there is a framework in Clojure inspired by this movement called Borkweb). So I realized that it is possible to build performant applications with almost no dependencies.

The most basic setup to start with consists of using ClojureScript to compile and transpile your Clojure code to JavaScript. You could start without any build tool such as Figwheel Main or Shadow-cljs, in which case you can choose to compile all of your cljs files into a single js file or else to do code splitting and compile to several js files.

I'm not entirely sure if code splitting could be the way to go for 'no build' ClojureScript without Webpack or anything like that. I'd need to dig into things like import maps, and probably a bunch of other stuff I don't even know about yet. That's something I'd love to look into later. But speaking of this topic, Mathäus Sanders' Borkweb project is definitely worth checking out.

Using Vanilla ClojureScript implies direct DOM manipulation, either via JavaScript interop or the Google Closure Library. While this provides a strong foundation for understanding how web pages work, it often leads to verbose object-oriented code. This verbosity can hinder the application of ClojureScript's functional strengths. While it's a great learning experience, it can be tedious for complex applications.

Things might get a bit hairy when you need to add some library from the JS ecosystem. You will have to check if the library you want to use is supported by the CLJSJS project or else you will have to add the externs file by yourself to your configuration. To be fair, CLJSJS has a lot of popular JS libraries and it works like any other Clojure dependency. For instance, if you want to use React, just add the respective dependencies to your deps.edn file. You’ll need to add Reagent too, a popular wrapper around React. As of today, you will not be able to use React 19, however, there is a lot you can do with React 18. And if you really want to take advantage of the newest React version, you could try a more modern React Wrapper such as UIx. I will talk more about these libraries under the SPA section.

Using build tools such as those mentioned above, however, offer a way better developer experience. It is important to understand what these tools do for you. Thomas Heller, for instance, carefully explains in this article what shadow-cljs brings to the table. One of the most prominent advantages, as far as I am concerned, is the integration with tooling. Often using raw ClojureScript can be cumbersome when integrating with your favorite IDE or editor, specially if you want to take advantage of the REPL. You can use raw ClojureScript to watch files for changes, but it is pretty easy to get out of sync with the state of your REPL and start getting weird errors.

You know, the REPL is amazing in Clojure, but in ClojureScript, it's not quite the same game changer. Modern JavaScript tooling offer hot reloading out the box, you can usually just save your files and see the changes. Still, for things like business logic and validation, the REPL is super helpful. You can really test and refine your code quickly. Even for frontend work, I'm a big fan of REPL-driven development.

In short, if you want to try Vanilla ClojureScript most of the time you are better off using some build tool (when not necessarily a bundling tool) such as shadow-cljs or figwheel-main since they integrate well with the npm ecosystem and provide a nicer developer experience.

Another approach that excels by its simplicity is Scittle. Specifically I tried the setup that Josh Glover described in his awesome blog. Everything is built with Babashka. A bb.edn file contains the tasks such as spinning up the http-server via io.github.babashka/http-server and starting the REPL. And then in your index.html you add your dependencies as script tags. The same goes with your source files.

Later I discovered a hot reload server called Cljs-josh. I will definitively try it later.

I really enjoyed this approach. I even wrote a small web app at work for the Marketing guys to update prices to the database. It’s currently running in production.

Now if you want to build something bigger, you might need something more robust. There are several options for building full stack applications, but the one I chose for pet project of mine was Kit. This is a framework in the Clojurian sense, namely, a curated selection of libraries with a plugin system that allows you to add what you need and that you can also extend according to your needs.

Kit uses Integrant as a state management system and it is very important to understand it to develop with Kit. At first I remember feeling like flying a plane; all of the configuration, starting and stopping the system… I mean, it takes some time to get used to it. But once you do it’s pretty straightforward. At another project I used donut-system which I find easier to use and I like it more. Just a personal preference.

Kit gives you tons of choices, but honestly, that can be a real problem, especially if you're just starting out. All those options can just freeze you up, and you can easily go down the wrong path picking libraries. I remember I picked XTDB and PostgreSQL, thinking I'd be able to decide which database to use when I deployed. That ended up with me building this huge data abstraction thing, which was a ton of work, and I'm not even sure it was worth it. I haven't touched that project in like a year.

Another questionable choice I made was to use HTMX and SimpleUI for the frontend. There is nothing wrong with these technologies or at least I do not have any objection. The thing is that while I was still learning the basics of Kit, I added HTMX and SimpleUI and it was too much of a cognitive load for me at that time. Besides, the farther you go from the defaults, the harder to get help when stuck or to find examples for guidance. I was able, for instance, to define simple components and wire them to a route, but when I needed to add other routes I did not know how to wire them together and I got stuck. I should have sticked to the defaults, at least in my very first Kit application. The advice here is: keep it simple as long as you can; you need to feel really comfortable until you take the next step and then go gradually, step by step, do not try several things at once.

I think Kit is great, though. I would recommend it, but definitively not for your very first project in Clojure. You must already be comfortable with some of the most popular libraries in the ecosystem.

I would also like to try Biff. Judging from the activity in Slack, the mantainer, Jacob O’Bryant is very active and quickly responds to questions. Besides, the feedback of other users is pretty good.

Single Page Applications

It is quite common I guess to use some React wrapper to write a ClojureScript app of some complexity. I already mentioned Reagent. It offers a fine development experience. One of the most pleasant experiences I had doing web development in ClojureScript was using this library. I believe is even simpler than plain React. You don’t need to care about hooks, you just need your Reagent atoms and that’s it.

Certainly, I have not written a big app in Reagent, but it is easy to see that handling a ton of atoms can become problematic. In cases like these you should look at a library such as Re-frame. I tried Re-frame once, when I started learning Clojure. The documentation is splendid. However, at that time I really did not understand the difference between a backend database and a frontend database. It took me a while to assimilate this, so I was very confused when the data I stored dissappeared sometimes (probably when I restarted the app) and neither did I have a grasp of Local Storage or IndexedDB. Back then I barely understood the DOM and too many basic stuff to be fair. Maybe I should try Re-frame again…

UIx is also really nice. I had the chance to try it out at clojurescript.studio (now hosted at https://studio.learn-modern-clojurescript.com) and also learned about Tailwind. I really like how TailwindCSS integrates with the locality of components. I mean, you do not need to read a .css file in order to understand how a certain component is styled, since you are able to see it in the component itself. Just do not forget to run the script that watches the changes to your .cljs files or else you’ll find yourself scratching your head due to changes in style not being rendered.

Being a modern wrapper around React, UIx supports React 19 and if you are already familiar with React, maybe leaving aside the syntax if you are new to Clojure, you will probably find it very intuitive. Certainly, I would considered it for a serious project.

Another option that looks quite solid is Fulcro. I believe that, intellectually, from its design and conception, this is one of the most interesting frameworks in Clojure(Script). The basic idea is that the UI is a function of data. As a consequence you are compelled to think of your application as a graph representing data flows, wherein each UI component will represent a node. Then you need to feed data into this UI graph. For that purpose you also need to build a corresponding graph in your backend with the help of the Pathom 3 library. Easy for you to say, you might think. And yes, it can be challenging.

In order to get a grasp of Fulcro you need to understand Pathom and EQL, otherwise you will be lost. And you better listen to the advice at the Fulcro Community Site:

There is a lot of functionality that Fulcro offers. Routing, UI state machines, RAD, …​ . Do not get distracted by these. Focus on using and understanding only the basic building blocks first. Once you are really comfortable with them and have internalized how the component tree, client DB, queries and idents play together and are able to build non-trivial applications with them then you can add some advanced functionality. But if you try to use too much too early you are likely to get overwhelmed and lost. So keep it simple and stick to the basics first!

Indeed, Fulcro offers a lot of tools. There is Fulcro Inspect, a vital tool for developing and debugging Fulcro apps, Fulcro RAD for Rapid Application Development, Guardrails for defining specs and validating functions at development time, UI State Machines and more recently an implementation of Statecharts, namely the SCXML Standard. I ignore if I might be missing something else. But it is a lot to swallow.

I tried to follow the advice. I started simple. I completed the Minimalist Fulcro App tutorial and then I tried to rewrite in Fulcro a piece of a system we have at work and got stuck implementing forms. Yeah, forms! I think what has bitten me is getting the data model wrong. But anyway, I am currently working on this project and still learning.

If you don’t like React or simply believe that the problem you are trying to solve requires something else, the ClojureScript ecosystem also offers some libraries not based on the Facebook library.

I had the chance to try out a library called Hoplon. It builds on top of HLisp and Javelin, which defines itself as a Spreadsheet-like dataflow programming in ClojureScript. Basically, in order to achieve reactivity instead of atoms you use cells. Although a bit different form everything else, you can pick it up quickly. The documentation is concise and covers the basics. I wish it had more examples or maybe a repo with example apps (at least, I could not find one the moment I searched). Unfortunately, I haven’t had the time to go beyond the basics and build a whole app in it no matter how small.

And of course, there is also Electric. Electric, it seems, introduces a new paradigm, unifying backend and frontend development. Within a single function, you define which expressions will execute on the server and which on the client. To be honest, I'm still working to fully grasp the details. Unfortunately, I haven't had the chance to try it yet, but it's definitely on my list.

And the new kid in the block seems to be Replicant. It is just a rendering library, however, it is lightweight and seems pretty easy to use. Reviews so far have been positive. I would really like to give it a try.

Concluding remarks

The main lesson I learned from this journey is to master the basics first: DOM, HTML, CSS, HTTP protocol, JavaScript and then the Clojure core library, data structures and the REPL Driven Development. As a begginer one too often gets blinded by the shiny new toy, however, starting simple and from the first principles is what will pay off in the long run.

The second lesson is to choose the right tool for the right job. It’s easier to say though. This is something that comes with experience, namely, learning from your mistakes. But you also need to take chances, to experiment with other technologies and tools. And then follow the advice of those who have walked this road before: start simple and grow your app according to the needs of the users, everything else will come naturally.

The Clojure(Script) web dev scene is really booming! There are so many libraries and frameworks out there, and I know I'm missing a bunch. Sure, React wrappers are everywhere, which isn't a bad thing, but there are some cool alternatives too. Before you jump into a project, ask yourself: do you really need that framework? Just start simple and add complexity as you go. Luckily, the Clojure community really appreciates simplicity.