Library stack for isomorphic webapp? These decisions make me feel at sea

I have a question about how to write isomorphic web apps in Clojure. I want to make sure I start with a fairly well-fitting stack so I don’t end up in the situation I’m in with my big Angular app, 30kloc without a centralized state management solution. What I’d like is a system by which I write the majority of my app in CLJC files, so that if a user is using a browser that supports JavaScript, they’re served an SPA (PWA) with offline capabilities, but if they want, they can turn off JS and still get served a functioning website. I have extensive experience making complex mobile PWAs in Angular/Ionic and know Clojure, but have little experience with a complex web stack, so guidance and explanation would be greatly appreciated. I’ve read and worked through Yogthos’s book on Web Dev in Clojure, but that stack relies on a lot of REST norms and assumes relational databases. Because this is a new project, I’d really like this to be pure Clojure with a Datomic database as a back-end. Or, if it’s simpler, something like it. I like simple and composable things I can learn in parts, abstract complexity, then forget about implementation details. Ideally the data model would be isomorphic on the server and client, and normalized.

The scope of the project is a kind of academic note-taking system that will eventually work offline. It’s kind of like how Datomic works with facts, but for writing academic nonfiction. (Think of a zettelkasten that a 12 year old could use.)

From what I understand, a way to do this would be to use Rum and DataScript to build a front end in an SPA. Rum works server-side so this shouldn’t be a problem. Would I still use DataScript, writing the client as I would assuming it were being served as an SPA, but would just be rendered server side and served as static files? I assume that means some more interactive elements don’t work. Or do I use a kind of reader conditional for my components, writing their structure in Rum but conditionally calling different functions or transactions based on what interpreter is doing the reading (client vs server side)? If that’s the case, then I assume I do something like read from the database in the server conditionals and read from DataScript in the client conditionals?

Is there a good guide to something like this, some repository I could work through? I need authentication and authorization, ideally in a way I don’t have to think a whole lot about (something like Devise & CanCanCan would be most welcome, which seems like Buddy). Any recommendations for resources or repositories I can look through would be most appreciated.

Also, is this whole Rum + cider + DataScript + SSR basically an IKEA version of Fulcro? Should I just be looking at that? I’m working through the Fulcro book right now, and feel at sea when I look at a lot of the code specifically, though I like the architectural decisions. (Plotting the UI as a state machine seems… hard to reason about or rapidly iterate. But again, I’m new at thinking that way.)

In terms of cutting things, I can give up the isomorphism, but I’d be really nice if I didn’t have to. I saw this thread (When is it best NOT to do a [reagent] SPA?) which discussed building out the basic functionality of the site using a traditional multi-page application while still using Rum components, then slowly building it into an SPA with reader conditionals. I like that as a model.

Any guidance is much appreciated, including the advice that I’m way overthinking this and should just start with Rum and cider and DataScript and consider Fulcro when I start feeling pressure, or just dive into Fulcro more. I am unsure of myself in Clojure, so people with experience are my lantern in the dark.

This is a very good question, & I don’t think I can expand much on your thinking

What I can say is I’m intending on learning Fulcro too, state machines is one of the pieces that I don’t have a good sense of, with my lazy investigation (the scale of Fulcro does make it feel a little bit of a chore)
and how the normalisation works in terms of feeding in new information

I’m hoping Fulcro RAD will be like how Drupal defines its systems (router/forms/schema etc) as data structures, and that I can not worry too much about the implementation details

The other side of this is perfect is the enemy of good, maybe Rum will get you as far as you need to go, if not maybe a failed Rum project is what is required to motivate the use of Fulcro, Fulcro appears like it could certainly over complicate a small project initially

I do wish Fulcro was split up and taught step by step, introducing new parts when the need arises but understandably the devs can’t do everything, on the bright side the book is very comprehensive and the slack channel very responsive, it’s a hard decision

I have a similar feeling wrt. Fulcro. It looks awesome, but it’s a large amount of info to digest at once. I found Tony Kay’s Youtube series for Fulcro3 a good starting place:

If you find your project ambitious enough, I think there’s no better choice than Fulcro. It simply offers consistent, full-fledged features for serious SPAs.
People mentioned lots of benefits of Fulcro in the other clojureverse convo you posted, so I won’t repeat them here.

Regarding your other questions:

Fulcro template (and the official doc!) does offer sample code for authentication and authorization.

The Rum+Datascript doesn’t have a neat story for server-client side communication. Blindly copy all server side Datomic datoms to client is not safe. Libraries like posh don’t offer much leverage, you still have to worry too much about authentication and authorization. The level of abstraction at which datoms are is simply not intuitive for auth(z) problem. And datom filtering is not performant at all. Edn-query-language, the Clojure native version of Graphql, is the higher level of abstraction that works well with server - client communication. And that’s the language Fulcro speak!

1 Like

Btw, Fulcro’s query-component coupling nature makes the components self-contained. You can develop pieces of your apps quite independently. At the same time, refactoring is a breeze. That’s what you would wish you had if you started with any other frameworks.

1 Like

ROCA (Resource-Oriented Client Architecture) design sounds like it’s relevant for you

This is an interesting question for me as I have experience with Clojure SSR for a SPA on a previous project, and on my current project I’ve gone completely down the rabbit hole of a pure SPA with Fulcro 3 with no SSR, but I am wondering whether it would have been better if I had approached it more along the ROCA principles.

There’s no single Clojure example that I’m aware of that is a best practice or example of what you’re talking about.

One thing that’s been a big win for my current project is I model my domain using plain EDN data in my own model format. I made a simple library that can convert this model format into Datomic schema and Datascript schema, and the server converts it into generated Lacinia GraphQL schema, and generated Pathom resolvers (the server-side of Fulcro 3). In the client, I have some meta components that can be used for a wide variety of these model objects by using the model-spec at run-time to generate table columns and cell types, special filters, lookup dropdowns, etc. This is similar to how Fulcro RAD works.

If I were to write the whole app from scratch, I would focus more in the beginning on the ROCA idea of beginning with the URL, and ensuring that correct EDN data is served via Pathom resolvers when requested with an “.edn” file type. This gives you a REST API too. Additional features of EQL like specifying a subset of fields or filtering for certain IDs can still be handled as POST params, and gives you the benefit of being able to interact with the API via curl or any other client.

Once the data model is working as an API, then I would add server-side HTML rendering. If requesting a “.html” file type, then server-generated HTML (using Rum or Fulcro) is sent over based on that data. You could even just start with that and be done. Or, if a SPA is really needed, once the client javascript loads the data, and re-renders the page, client-side routing changes to the URL will call the relevant “.edn” URLs. This can all be accomplished in Fulcro + Pathom. I haven’t used Rum for something like this so I won’t compare them.

It is a lot of work, and if all you need is a bit of dynamic behavior on the client, it’s hard to justify. I agree with a comment on your linked page that Phoenix Liveview seems like a very innovative and promising approach. I think someone is doing something inspired by it in Clojure:

For off-line first dev, seems like a great place to start, combined with the ideas above about starting with a basic REST API.

This topic was automatically closed 182 days after the last reply. New replies are no longer allowed.