Can someone genuinely explain to the the desire/interest to use HTMX/Hotwire/LiveView etc?
I was writing web apps when rendering templated HTML views on your server was standard, and you had controller endpoints that returned HTML content, or things like Rails "Unobtrusive Javascript" where you had code like:
Whether or not you love or hate this model of development is irrelevant, because if at some point you need to render on a client other than a browser -- you have to start from the beginning and write an API now.
IE, so that your Android/iOS app in Java/Swift or whatnot, can ask your backend for data and render it using the tools available on that platform.
When we turned apps into pure API's and just exchanged data with them, it opened up the ability to be platform-agnostic.
I guess I don't understand why someone would choose to build an app that sends HTML over the wire instead of JSON/XML/whatever, that any client can render. All it means is that you have to write this functionality later when you want it, in addition to now having this HTML stuff.
First, many sites do not need a mobile app — people who sell apps like to present that as a requirement but there large categories where it doesn't make sense and most users won't install it because it takes space on their device and they [correctly] fear ads, notification spam, and possible invasions of their privacy. I've seen figures for this where pretty well-known organizations spent millions on the app and then saw install rates under 10% with most users opening it only once or twice.
Second, this is something of a false dichotomy: if you have a website, you need to have HTML somewhere. One approach is to use an SPA but that increases the cost somewhat significantly and absent a significant engineering effort will reduce performance and reliability because you are moving functionality out of a high-performance data center where you control everything onto the users' devices where you have to work with whatever browser and extensions/ad blockers they have installed.
An alternative approach is to render HTML on your servers — quite possibly using the same JSON API as the source — and then progressively enhance it with the functionality which makes sense to do on the edge. This gives you a hefty performance win for the initial page-load times, helps with things like search engines, and ensures that your users get at least the basic experience when something goes wrong with client side JavaScript (network errors, browser compatibility issues, ad blockers, etc.) rather than nothing working at all.
> First, many sites do not need a mobile app — people who sell apps like to present that as a requirement
Yes, Testify! Also, I'd like to mention, so many of these upsold apps end up being a wrapper around the normal web view. I've seen this being advertised/proposed as a plus, as well "No need for your customers to learn a new interface, we specifically engineer your app experience to be just like the web experience". It's interesting to see heads nodding in reply to this.
Oh, yes — definitely saw those. My favorite were the ones in the pre-WKWebView era where the app was basically the website, except slower because it didn't have the JavaScript JIT enabled.
The idea is discoverability. Once, people looking to purchase Nespresso coffee online would go to Google. Now they go to their app store. If your app isn't there, they those people are going to install a competitor's app and buy your competitor's coffee.
Do they? Really?? I know some people who don't even go the the store for apps and do everything through the Google app that includes app results as well, but the other way around?
I just searched for "buy coffee" in G Play and not a single one of the top 15-ish results let me buy coffee apart from one coffee shop in New York, which is off by roughly one half globe circumference. If anyone ever did that, they'd quickly realise there's nothing there and go back to a search engine.
According to the guy who does metrics for my client - and his company does metrics for the largest e-retailers in my country - this is the case. Maybe he's pushing his own agenda or dream? Maybe it's market-dependent? But this is a company who can be trusted in the industry.
Wouldn't be the first time industry research results were conveniently aligned with profits, but I'm obviously not accusing anyone of anything as my evidence is 100% anecdotal and any research is better than none.
I guess it's just that users are stupid? Like, I know calling users stupid is a bad trope and all, but this is one of those cases where the behaviour just straight up wouldn't produce usable results. Like, if they're looking to buy a bike, will they just search for "bike buying apps"? I get looking for "apps to buy things with" (like amazon, wallmart, whatever) and then picking one and seeing if they offer bikes, but looking for the product directly??
This actually seems to also contradict the recent trend of people moving to big apps like DoorDash and Amazon replacing individual apps with different niches like the McDonald's app and apps from smaller retailers. If you're looking for "buying a bike" the Amazon app won't even be on the list...
Again, all of this is my logic, I have no evidence. This is just one of those situations where something goes so strongly against my intuition that I can't imagine how it could possibly be correct. I'll have to look for some real research on this, if any is available...
I’d really want to check the sources on that to see the methodology. I’m in different spaces so it could be different here but that’s pretty much the polar opposite of what I’ve seen – people use social media or web searches, and hit the App Store only when pushed with a hefty drop-off rate. I’ve heard that this is very different for a few categories like games, which makes sense, and I think a key part is how well you can sell the benefits to the user: games, Uber, etc. have a pretty clear benefit but it seems like installing an app is seen as committing to some kind of ongoing relationship.
Is this really the case? It never occurred to me but I'm web native, not app native.
If it is, in most cases we can just release a webview-in-an-app and render the usual responsive website on the phone. This is the low cost route to apps.
However there are products that could genuinely need an app. A Nespresso machine could be one of them: control everything from your phone and if a machine has some kind of cup automation, also make coffee from remote and walk to the machine to get the cup whan it's ready.
I agree with the sibling comments, I've never actually seen anyone go to the app store before Google/Amazon for simple purchases.
When it comes to these companies selling apps though, it doesn't matter what the market thinks... What matters is what the suppliers think the market thinks.
I am open to HTMX. Seems refreshing but I am a little confused about how to structure the backend API returning small bits of HTML? Wouldn't that be a colossal nightmare to reason about?
I can think over time I'll have endpoints like this:
POST https://foo.com/bar-section/baz-button
No? Haven't quite thought about it deeply but I can imagine logic mixed with UI bits in the backend.
Have a look at how Phoenix(Elixir) does it with LiveView. You simply write everything as a server rendered view and the framework pulls things apart and sends just what changed over a web socket. Its mind blowing how fast it is (can be. Latency is real)
I have no doubt that this style of front-end management is going to be a game changer in the next few years. Of course full-on SPA frameworks will still have their place. But if you aren’t writing the next Google Maps clone, maybe you don’t need all that. It’s made me love web dev again after hating the complexity explosion of recent years.
What worries me (I write Django, Rails, Phoenix web apps - different customers at the same time) is that I'll have to learn yet another three different ways to do the same thing. What I'm doing now is either creating HTML pages (the 3 ways are pretty much the same, Django a little bit more set apart) or a JSON backend (basically the same everywhere.)
But to be fair it's fear of the unknown: I read about all of these approaches but the only new project started with a SPA and the other ones will never move from backend generated HTML.
The endpoints that return endpoints are not API endpoints, they are UI endpoints like `/index.html` or `/login` or `/Results.aspx?id=3984272`. Now you can have `/login/email` or `/login/google` and switch between them like <https://htmx.org/examples/tabs-hateoas/>.
As for the machine-consumed API, you have quite a few options. e.g.: The server that serves these resources may serve JSON to the same URLs through content negotiation, or the API may be separate, or you can have a generic API that is consumed by mobile app, desktop app and HTML-producing server (as opposed to JS app delivered by static file server or framework SSR).
The UI runs on the server, using REST to change client state over a stateless protocol similarly to how a React app may use Redux.
Not really, as long as you keep your HTML-driven interactions from becoming too fine grained.
For example, if you have a search page that provides management of contacts + active search & click to load functionality, it might look like this:
GET /contacts - get a list of contacts + search & click-to-load functionality
POST /contacts - create new
GET /contacts/:id - get the details for the contact
PATCH /contacts/:id - update the contact
PUT /contacts/:id/archived - archive the contact
DELETE /contacts/:id - remove the contact
All pretty reasonable and all can reuse the same few templates and sub-templates for rendering responses.
Thank you. To continue this conversation, how would one implement searching and filtering? My use case is an online ecommerce store (think amazon.com or newegg) where you sell multiple different categories of items. For example, if someone searches for SSD, we should show a list of checkboxes to filter by capacity such as 1 GB or below, up to 127GB, ... (based on inventory and other business requirements). How would I do that?
In your example, we can say something like if you could grab lists of by contacts by some common trait such as employer or domain of email address?
You'd have category pages with different filters. E.g. for the 'Storage' category you'd have, among others, the 'capacity' filter. HTMX doesn't take over your entire render like a SPA. It affects targeted areas of the page. E.g. when you would select the '<1GB' checkbox and click 'Filter' on the left panel, that's where HTMX would step in, to swap in the response of calling the 'search' endpoint with the current filter params, e.g. '/search/storage?capacity=<1GB'.
I think you can get pretty far by having good conventions and not over-thinking it, but remember also that this is all about being pragmatic instead of taking a one-size-fits-all approach — if you found yourself doing a _ton_ of highly-granular requests, that might be a clue that you're not in the sweet spot and an API client might be a better fit.
The trade-offs in terms of how granular to go will always exist -somewhere-, whether here, or in terms of react/etc. components, or template include directives.
I suspect 'template include directives, except with some client smarts' may be a good (approximate) model to use to initially get a mental feel for how stuff like htmx plays out in practice.
Unless we are speaking about games (WebGL/GPU are never going to compete with GL ES 3.2/Vulkan/Metal capabilities), or hardware access not exposed as Web APIs, all CRUD apps and magazine/newspapers content can be delivered as mobile Web.
When was the last time you used an application from a gallery, archive, museum or library? How many people use https://apps.apple.com/us/app/wikipedia/id324715238 instead of visiting wikipedia.org? I'm not naming names but this came up a number of times at meetings I was involved with in the GLAM community where people mentioned having spent considerable amounts of money on apps, even winning design awards or getting featured in the Apple AppStore, and then seeing that their daily active users never came within 4 orders of magnitude of their web traffic.
What they generally had in common is that they're content-heavy (i.e. solidly in what the web was designed for), might not require authentication, and most importantly aren't something most of the users wanted to use all of the time. If you are trying to get the hours for a tourist destination or even a restaurant, look at public information, etc. the time needed to login to the app store, authenticate to approve the purchase, wait for a large file to download, open the app and complete any mandatory enrollment, wait for it to retrieve content, etc. was often orders of magnitude slower than using the website. Restaurants and bars are a great example of this: if it's your hangout and they have something like integrated ordering, maybe a trivia night or something like that, the app can be useful. For most people, however, a webpage with the contact info and a menu with Apple/Google/Stripe buttons to pay is going to be better than an app they use a couple of times a year.
The other key thing to understand is that this is a question of priorities and budgets. It wasn't that there is no possibility of doing anything useful for these organizations in a mobile app but rather than the cost wasn't worthwhile for the incremental benefit to the users, especially when you considered the opportunity cost of not spending that much money on something else, especially since most of the cool things you could do in an app could also be done on the web with very similar quality but with the difference that you only had to build one version rather than 2+ and the web version required much less ongoing maintenance.
Hn would be worse as an app, but more notably Twitter, and reddit would be better as pure web pages if they didn't purposely cripple them to funnel people into the app.
I for one will never download an app when I can just use the mobile website instead. That's what modern websites are — they're single-use disposable applications. Why would I throw that away for a native app that eats up disk space and has much greater access to my device?
> Why would I throw that away for a native app that ... has much greater access to my device?
You wouldn't, because you understand. But for every one of you there are 99 other who will install such a app if it is pushed to them. Apps are almost like bookmarks now.
Bookmarks with access to your location, contacts, and camera.
1) No need for 3.7GB of node_modules to crank out a simple site
2) Single solution, easier to bring on junior developers and reason about application logic
3) Caching much easier, too many people think they need real-time push when NRT-polling + caching is far easier and more performant
Broadly speaking it's often a case of YAGNI. The JS ecosystem really does still suck for its complexity, I remember that every time I ramp up a new developer onto a React project. It's smoothed out with tooling but the litany of JSON configuration files and the mess created by hot-reload + proxying + microservices etc etc. Often just YAGNI.
People today don't remember the VB6 IDE days where you could slap a button on a Form, write a line of code for a MessageBox, and smash that play button with zero effort. JS development is often about working your way through a tangled spider-web of helper utilities and config files and JS modules at which point when it finally works you're afraid to rev anything.
a bit focus of htmx, alpinejs (and htmx's sister project, https://hyperscript.org) is putting the logic on the element, so you can see exactly what a button, etc. does just by looking at it, and without trawling through ten files of indirection
i am trying to popularize the term Locality of Behavior to describe this design goal:
I'm curious about your thoughts on that locality of behavior idea vs aspects of precision and composability. For example, the argument goes that the behavior of `hx-get` is obvious upon inspection, but the terseness is achieved by omitting the event type (i.e. it's assumed/implied that hx-get runs on click). The quintessential widget where that breaks down is the table w/ checkboxes for batch operations over an arbitrary subset of rows. That's where less DSL-ish frameworks' abstraction via state management tend to shine, since they allow the developer to more precisely specify how disparate checkboxes relate to a bigger picture that may also involve table sorting (both client and server varieties) or nested modal structures or other forms of complexity.
Button should not have any behavior, it should generate an event. Your app can have one or multiple listeners for this event. This pattern is battle-tested for decades.
2.) I know what reactive programming is, I make extensive use of RxJS at work. And I obviously know what an event listener is. I was asking why is it wrong to have the button have functionality and only emit events. And I did google that, but nothing from your comment you posted that I googled lead to anything about reactive programming.
If your node_modules is 3.7GB, you are doing something seriously wrong. My node_modules for a 100k+ line app is 243MB and that includes all my dependencies, compiler and testing frameworks which seems very reasonable. All compiled down to a 4MB blob which seems pretty reasonable for a big app :)
243 MB?! If that's all code (which is unlikely, sure) and it has an average line length of 100, that's 2.4 MILLION lines! Do you really need that, (no), or have you just 'stockholm syndrome'-d yourself into believing this is not a problem?
I just tried running WinDirStat on the `node_modules` folder in a Create-React-App project I had lying around. Total size was 234 MB. Of that, 129MB was JS files. By far the biggest piece was TypeScript, which has 45MB of .js in 6 files making up its parsing and IDE language service implementation.
Or, take Redux Toolkit, which I maintain. If you look at https://unpkg.com/browse/@reduxjs/toolkit@1.6.2/ , the published package for our current version, it adds up to about 10MB on disk. But, that's because we ship three separate entry points (RTK core, RTK Query UI-agnostic core, RTK Query with React-specific additions), and for each entry point we compile the code to multiple file formats (CommonJS, ESM legacy, ESM modern, ESM with "dev" and "prod" already built in, UMD dev, UMD production), and each of those has sourcemaps. The actual amount of code that ends up in your bundle is about 20KB min+gz.
So, it's a combination of many things: TS itself is big, libraries typically publish packages containing both the original source code plus multiple build artifacts to run in different environments, and packages also include various metadata and other files as well.
I'm not saying this is necessarily a _good_ situation :) Goodness knows that JS dependency trees _should_ be much smaller. But there's a lot more nuance to it than just "245MB of code wut lol".
There's definitely an anchoring effect going on. I remember when dart came out and people were like "lol 100kb hello world, wtf" and now people say "4mb app is reasonable" and nobody bats an eyelid.
Heh, that's actually part of the point I'm making - big difference between "size of build dependencies unzipped and extracted on disk", vs "size of actual app bundle".
As an alternative view, while living in a very unremarkable North American area I have atrocious internet service. I don't think it's particularly uncommon, but it certainly is enlightening to browse the internet. Depending on the time of day a 4MB blob by itself can take ~8 seconds to download. 8 seconds is a _long_ time.
Obviously I have no idea what your app is, I have found very frequently it is not reasonable to have blobs of similar sizes (especially if text content waits for some front end framework to download/initialize).
Certainly. My hope is things like WASM eventually make Javascript obsolete if we're into pushing giant blobs of client UI over the wire. Blazor / Bolero in C#/F# are promising starts in that direction.
As a developer who worked both with Windows Forms and React, IMO the only explanation for longing for the former could be that lovely nostalgia feeling when you already know what react offers.
I think people approach it with prejudice (ew, Javascript!) and never properly learn it.
There are projects where react would be overkill but many benefit from the clear separation of the front end.
Also configuring a react project is easier than many other projects I've seen in my career. People just love getting internet points by constantly talking about left-pad, node_modules size, config files etc. I personally know how crazy managing maven or gradle (.gradle folder hell) or nuget or composer or pip (venv or virtualenv...) or [name of your favorite package manager] can get, so I'm thankful for the great community that work on the problems with the npm.
I believe the Model View Controller or Model Template View architectures provide sufficient separation of concerns without requiring separate codebases, teams, tooling, and project management.
I didn't mean the classic separation of concerns. I meant that front-end development should be taken seriously enough to (sometimes) warrant its own focused team.
A proper FE developer who knows about accessibility (not just ARIA-attributes but also contrast and usability), UX, semantics, and also being capable of delivering performant user interfaces is rare these days, because people think full-stack development really is a thing. I think it is one of those mythical things like a 1000x engineer. People think that they can get away with 90s style websites, but please test stuff with a screen-reader, and also while you're there get some front-end consulting, you will be surprised how badly you may be doing. IMHO, many need dedicated FE development. Whether they use React or not, I think many do. You can distribute many responsibilities on non-technical members of the team, but then you start thinking "couldn't they just learn a bit templating so I don't invest so much time on implementing their seemingly never-ending requirements"!
BTW, this separation is of course also possible with your traditional MVC stack, which I also use in my personal projects a lot, in which React is indeed overkill, and there are many cases that a bunch of templates deliver all the things you may ever need. If it gets complicated enough some day, return your View Model as JSON and there you have a perfect API. IMHO REST APIs aren't suitable for any dynamic front-end development anyway.
The only reason I wrote that single sentence in my previous original comment is that React forces your hand a bit, in this matter. It's a small point among other things.
Doing a ground up rewrite of our app at work. I made the call that the application needed to be semantically correct (well, mostly correct. We avoid div everywhere), have proper tab order, and be screen reader accessible. It takes more time but honestly it's not that difficult to do. Most people just.... Don't do it.
It doesn't end there. Even caring a bit for that makes a huge difference, I agree, and I appreciate the effort. The last time we had an accessibility audit, however, it was consisting of more than 200 problems with different priorities for an app with 70 something views. Our FE people were immediately up to the task of getting them fixed; a motivation I simply cannot expect from anyone who doesn't have a front-end focus. My basic checker reports in this simple comment-editor already a couple of problems.
It also doesn't end there. There's the UX part, re-usability (huge difference to have a cross-team front-end service-team), performance (not just initial load... Are the widgets you build with, say, Alpine.js tested for performance?), development speed, caching, offline-capabilities, opportunistic rendering, other best practices, branding, consistent design and interaction and so on. It's a science on its own.
Front-end is not just some basic thing which got a complex solution for no reason. It is a complicated task.
1) Who cares about the size of node_modules? What is the reason to care about it at all?
2) “Junior-centered” app has to be simple as a “ToDo list example”, all the time. It's not possible for the real apps - they constantly evolve, and to keep their code readable and clean you need skilled developers, not the other way around.
Worth pointing out that HTML is more ubiquitous than you're making it out to be: just plop a WebView in your native app, and you're done. I know, for example, that the Uber app does webviews in some places; you probably wouldn't even be able to figure out where just from looking at it as a user.
Another case in point: is your dev team going to support PSP devices[0]?
> because if at some point you need to render on a client other than a browser -- you have to start from the beginning and write an API now.
The answer lies in the "if" there. The speed and lack of complexity developing server-side with no API is unparalleled to doing client -> api -> backend. Not to mention, you aren't tying yourself into several different JS technologies (Which GraphQL or REST lib? Which view framework? What testing frameworks? And what's the whole backend stack? etc. etc.) I can only speak for LiveView but the whole top-down experience is in Elixir with sprinkles of vanilla JS (the community is big into Tailwind and AlpineJS as well, but not exclusively).
I have real world experience of dev'ing API-first only to have the company nix any plans to open up the API. Adding new features is always a slog as we have to think about if the business logic needs to be on the client, the server, or both... not to mentioning have to update about 18 different places the name of a new field you're adding.
The company I work for actual has a LiveView widget that is embedded on their client's apps. I can't speak very well to it since I only just started working there and don't actually start until next week, haha.
But ya, I'll also echo the whole idea that a lot of companies don't need mobile apps. I'm certainly one of those people who uses the browser on my phone whenever I can.
- flexibility: e.g. because of the uniform interface, versioning is much less of an issue
- power: e.g. deep linking is trivial
Additionally, there is increasing awareness[1] that Application APIs and Data APIs have different needs and "shapes", and, once you have split them up, reconsidering using hypermedia for your application API[2] is a reasonable thing to do
That handles a few things like pagination for list views, calling get_absolute_url() on objects in the template context which have that method defined, and running all URLs through request.build_absolute_uri().
> That said, many projects don't have or need mobile apps, and many more can probably get away with a web-first mobile app.
To me this is like building a house that can never be expanded, instead of starting with an open-ended design.
> For those apps this stack can vastly simplify and streamline development
I guess this is an opinion/subjective?
I am very open-minded and experimental. I love trying new things.
I've tried these technologies (HTMX, it's precursor Intercooler.js, Turbolinks + Hotwire, LiveView, etc) and for me they are less productive than the ways I know how to build API-first, JavaScript web apps.
Have nothing against them, just genuinely having a hard time seeing the appeal.
I've tried these technologies ... and for me they are less productive than the ways I know how to build API-first, JavaScript web apps
I think I feel the same way you do, except the other way around. I genuinely can't understand how you build apps productively with a separate client/server model.
How do you do form validations? How about authentication and authorization? What about a consistent domain model? Is there one copy of it on the server and one on the client? Or does your client just treat everything as dumb data and make network requests for every question of business logic? What about data consistency throughout the interface—when you update e.g. Document(id=45) with new data, how does that update get propagated to every other place in the app? Does that involve a client-side identity-map? Or are you manually trying to send updated data to other places with callbacks/whatever? etc. for ever and ever.
Every time I try to build an app where the frontend and backend are split into a separate client and API, it ends up adding so many more considerations and so much more complexity than the traditional model. Mostly because now everything that just required a database query or method call before now also requires a network request, but also because data consistency in a long-running app is so hard to get right.
Those are all questions I have, as a 2-decade "traditional web app" developer who is now doing frontend work & hybrid mobile apps. I know the backend sucks for some of them too, so this isn't a set of questions to bash with.
They are the questions I'd spend money on a book or learning materials to answer. They are philosophical questions that seem overlooked.
I think the answer is JS/TS on server and client-side for a lot of them, so models/classes/structs are shared; form validation rules are shared (something let's face it that "traditional" web apps suck massively at).
The one Q I feel qualified to answer is:
> What about data consistency throughout the interface—when you update e.g. Document(id=45) with new data, how does that update get propagated to every other place in the app? Does that involve a client-side identity-map? Or are you manually trying to send updated data to other places with callbacks/whatever? etc. for ever and ever.
Assuming you're not talking realtime (because that's a problem for both) then this is why the concept of data stores (Redux, Vuex, Mobx to name a few) are so popular. Because when the data is changed, and you're using a reactive frontend framework, it updates all components that use that data. It is, frankly, magical and wonderful. But as you say, data consistency can be a problem...
How do YOU do form validations? Is it entirely on the backend so it requires a round-trip before the user gets feedback, and additionally requires you to wire both the HTML endpoint for user feedback AND for rendering some results?
For myself, I have a reusable Form component I can plug in arbitrary tests. You can run the tests when the user changes an input or on submit, with some options/conditions for when you clear the feedback.
Then, of course, my API gives feedback as well; usually, it's harder / more awkward to wire the API feedback to highlight the offending Form fields, and I certainly don't see how HTMX would accomplish this, so without some machinery I already feel your side lacks QOL features like "highlight error fields" and "autofocus first offending entry".
> How about authentication and authorization
You are joking or what? There is no difference in how an HTMX app would authenticate to its endpoints and how an SPA would
Finally, you ask a bunch of questions that clearly aren't any better in an API-endpoint-randomly-renders-HTML paradigm...
> Is there one copy of it on the server and one on the client
Obviously, but you load the client version from the server at API request time... just like an HTMX powered client... except an SPA keeps objects instead of a rendered representation so you can do more in-depth things without network trips...
> What about data consistency throughout the interface—when you update e.g. Document(id=45) with new data, how does that update get propagated to every other place in the app?
Because you use singletons like a not-crazy person... how does this happen in HTMX world where you have no data representation on the front-end? Does your form have to be manually wired to update every single present data view that is holding that document? Seems like you're worse off than me. I hold a stateful representation and update the views when that representation changes, automatically... Wow, literally no work required because it's declarative
> Does that involve a client-side identity-map? Or are you manually trying to send updated data to other places with callbacks/whatever?
Laughable. the HTMX paradigm is clearly worse off here
> Every time I try to build an app where the frontend and backend are split into a separate client and API, it ends up adding so many more considerations and so much more complexity than the traditional model
You clearly did not bother to understand how state & views relate in an SPA then. Your complaints make no sense either, this happens in HTMX world more than mine:
> now everything that just required a database query or method call before now also requires a network request, but also because data consistency in a long-running app is so hard
I honestly can't tell if you're trolling me. Let me break it down for you.
In client/server world, client asks APIs for the data. Easy. Imagine a paradigm like GraphQL, the client literally just loads the data. It's the same as it would be on your server endpoint for HTMX, that's literally the client in your world. I hold a global singleton representation if my app is interconnected, or I load different singleton representations for unrelated subtrees if it's only partially interconnected.
Next, if "collaboration"/real-time features are important, you make this data loading "aware" of when the data changed server-side, and you refresh your data. Easy as pie; you use the same function to reload. Lots of options on how to update the data, but if you correctly make your "load" function idempotent it is clear this is trivial.
Finally, you write views that describe, declaratively, how to render the data. Since application state automatically re-renders these views, ideally not in their entirety if you used a good state management, you've already solved every single concern you listed about "stale data" by how you wired your data loading.
So, great, in conclusion, HTMX endpoints are like a scattered version of a client that makes all nice "application" tricks virtually impossible, giving you no gains in any fashion that relates to data consistency or continuity, and requiring loads of manual wiring and awkward data-HTML manipulation in places that have no business doing it.
I use some server-side library to do it (take your pick for your language/framework of choice) which then sends it back to the user, in most cases automatically wiring up old field values and error messages. Checking string lengths or whatever is equally easy in both cases, but what's really difficult is the part you conveniently hand-wave away:
usually, it's harder / more awkward to wire the API feedback to highlight the offending Form fields
This is the entire point of validations. When I render everything on the server, I have my entire application and its associated logic to play with; can this Person access that Document? Has this Person already enabled Notifications for new Revisions on this Document? etc. Most server-side libraries make it equally easy to surface validation errors for those questions as they do questions like "Is this Password no shorter than 10 characters." Every time I've tried to do this on the client there's been no easy way or you have to wire it up manually every time.
There is no difference in how an HTMX app would authenticate to its endpoints and how an SPA would
I've found this not to be the case. Here are the potential options I see:
- The client contains all the logic for who can view what, and redirects or shows a message appropriately using whatever mechanism is appropriate (e.g. Angular has route guards, I'm sure other routers have similar functionality). Because you want real data protection, this logic is also on the server, so now you have duplicated logic and you have to hit the server multiple times on every navigation, which is basically impractical.
- The server contains all the logic for who can view what, and when the client asks the server for some data, if it responds with 403 it can show a message or redirect or whatever. This is okay, but now you have so little information on what happened on the client side that it's a much worse experience. e.g. if I try to access a document that I don't have access to on the server-side, I can redirect_back and maybe show a pretty message that says "hey you can't access that, maybe click this link to ask for permission." If I just get a 403 from the server, all I can say is "uhh I don't know, you can't do whatever you just tried to do."
This is much easier if all your logic resides on the server. To check if a Person can access a Document, you just call person.owns?(document) or whatever. If they don't, you just redirect_to wherever, and since you haven't actually rendered anything yet it's all in a single HTTP request-response cycle.
Obviously, but you load the client version from the server at API request time
I'm not sure you understand my problem here; it seems like perhaps client-heavy people just aren't doing this anymore? Ember, Backbone, et al got this part right, and everything since then seems to have forgotten about the "M" part of MVC.
What I mean is that on the server I have a Document class, a Person class, etc. and the associated business logic for dealing with it. Maybe a document has a bunch of Revisions, and they're stored in an array or something and I can get the newest revision by calling document.latest_revision, or I can get properties of the latest revision by just doing document.latest.title. Maybe when I update a field on a Document I really want to create a new Revision or something. Whatever, I'm just making stuff up here, but you get the kind of business logic I'm talking about.
How do I do any of this on the client? Do I have a separate Document class that maintains all the same logic? Do I split the logic so some of it is on the client and some is on the server? Do I put it all on the client...oh but wait I can't do that because the server needs to do integrity checks or make database calls that the client simply can't do.
In an ideal world, what I want is essentially like my ORM on the server-side: a cache of every instance of every model that I've loaded from the server, normalized with references to one another and an easy way to retrieve an object with its relationships either from memory or from the database (network/API). It's then easy to update Document(id=45) in one place and have it propagate to the rest of the app, and if it's referenced by any related objects to have the update propagate to them, too. This is a hell of a lot of work to create, and basically none of the modern frameworks provide anything resembling this.
---
Your final few paragraphs are essentially all the rosy writing out there about client-heavy approaches: look how wonderful it is! Just fetch your data and we'll rerender it! I understand all that. And maybe it's evidence that we're just living in such different worlds that there's difficulty explaining requirements to one another.
If I was creating Desmos or something, where basically every ounce of logic exists on the client, and the client's job is just to do a bunch of stuff with client-generated data and display it, then yeah I'd probably reach for a client-centric approach. But I'm not. Most of the time I'm creating GitHub or Wikipedia or Fastmail or something, where there could be some nice benefits of having a fat-client that talks to an API, but the massive complexity increase just isn't worth it.
usually, it's harder / more awkward to wire the API feedback to highlight the offending Form fields
> This is the entire point of validations
Now, let's be explicit - I can effortlessly show an error toast describing those errors, and my client-side validations are generally meant to be a 1:1 match. I also say "awkward" rather than "not possible" because you can directly access the validation engine in my paradigm and set error messages for individual fields, but it requires the API endpoint author to be returning a data structure and not 400 'Requires some other thing' style.
And now I see what you are saying about doing this server-side, but the whole point of the client-side method is that for free you get this:
> in most cases automatically wiring up old field values
I get it in 100% of cases, for arbitrarily complex form elements. I also greatly object to this point:
> Most server-side libraries make it equally easy to surface validation errors
If I am using arbitrarily heavy code of course I wire these error messages perfectly. I will not concede any points about time-to-execute going forwards, I see it serves only to muddy the waters as you have strong capabilities
There is no difference in how an HTMX app would authenticate to its endpoints and how an SPA would
> I've found this not to be the case
I don't really follow your objections / cases. You argue that the server is always aware of who can see what, and this is transparently true, but you try to argue that the client dynamically loading who can see what from the server is a problem due to network round-trips yet the server-rendered app where there's network round trips for literally every user action isn't a problem. That is where my uncharitable tone is sourced from; you can dynamically return this stuff to your client with ease, and all you've done in an HTMX-style paradigm is force the user to accept the "bad case" outcome for client-queries-server nav in the best case.
You will have to go deeper if I am meant to see why the client couldn't just load some batched map of legal routes they can and can't see based on their authorization and update any nav options around the app (sidebar, page-to-page, etc.) accordingly. I regularly load configuration batches to configure my client-side applications, and you clearly execute extremely similar code to bridge the UI layer and ACL layer on your server. Moving this to the client is entirely unoffensive and standard web development work
To be precise, your example / motivation is something like:
> This is much easier if all your logic resides on the server. To check if a Person can access a Document
In order for the user to have a copy of this document to click on at all, I have made a network request to get some data about this document. If ACLs are important for this interaction, I load them at the time I load the document's data. For example, I have "view" vs "edit" functionality in some collaborative modeling app I've built, which is based on whether the user has read-only or write permissions to the model. To nest this functionality to gate parts of the model graph would be trivial, once the server implementation was complete. Thus I struggle to understand the difference between the server/client paradigm wrt this topic.
Obviously, but you load the client version from the server at API request time
> I'm not sure you understand my problem here
Again, I struggle to understand what the difference is between the client & server paradigms here.
> what I mean is that on the server I have a Document class, a Person class, etc. and the associated business logic for dealing with it. Maybe a document has a bunch of Revisions
Any relational data (and as discussed, associated ACLs) is transparent to deal with on the UI; the model is the data, and any exposed action gets a method that you will write to suffice its needs. If the data is somehow non-relational or requires something awkward that your server paradigm magically solves, I simply load it from an API your server exposes which can describe these things to me as it did to you and then I have those same capabilities. Have I omitted something in this case with this claim from your view?
> How do I do any of this on the client? Do I have a separate Document class that maintains all the same logic? Do I split the logic so some of it is on the client and some is on the server?
Whatever you like. Why would I prescribe this to you? I don't prescribe how you've done it on the server.
I personally create MobX stores to hold the data & relations (generally implemented using maps sending instance IDs to instances), and MobX classes at the "semantic layer" (so they might include a small part of the relational graph) and instantiate them with their instance data. As I discussed, I prefer the singleton pattern, so I do not keep multiple copies of the relational graph ever. MobX is nice because it ties the view layer updates directly to data updates, preventing unnecessary renders (fast), but the key is of course to integrate these objects with a stateful layer that will correctly trigger re-renders.
When it comes to observations like "the server needs to do integrity checks or make database calls that the client simply can't do" then it is clear you use an API at this point. While I understand you possibly have this "API call" for free as some server-sided library at the logic time of executing the action, the ease is only for you as the developer and the end-user will see identical performance between a client-side and server-rendered app in this case in terms of action completion time. In my described MobX world, things which are client-side manipulations are implemented as methods on the client-side, and of course if it is something that will require multiple server-side actions I just offload it to an API so you fall back gracefully into however you would already have done this.
Again, if I am omitting something somehow please say it, but I find it immediately clear from what you describe a trivial solution to client-siding your code would be: the exact times to make an API call are when your server code uses your nice server-library-provided capabilities, and the times it doesn't can be implemented as a client-side method.
> In an ideal world, what I want is essentially like my ORM on the server-side: a cache of every instance of every model that I've loaded from the server, normalized with references to one another and an easy way to retrieve an object with its relationships either from memory or from the database (network/API). It's then easy to update Document(id=45) in one place and have it propagate to the rest of the app, and if it's referenced by any related objects to have the update propagate to them, too
Yes, this is what I accomplish with MobX stores & classes. I have 1 copy of everything, I keep the lists of relationships on the class or it's directly query-able thru my stores (& their maps), and anything which "walks the graph" to render some related object of course actually loads the related object's class instance in order to not be stale.
> This is a hell of a lot of work to create, and basically none of the modern frameworks provide anything resembling this.
You see why my tone was uncharitable then, perhaps.
> Your final few paragraphs
I appreciate your in-depth response, I truly do, and I hope this conversation helps you understand why I hold my view and perhaps find yours difficult to understand. Any server-side-magic you use, which I truly recognize exists, is the place where API calls happen in my clients. It's really that simple, and the net result is way less or way better disguised network round trips than HTMX from the user perspective.
> Most of the time I'm creating GitHub or Wikipedia or Fastmail or something
Most of the time I'm creating what I've come to hear referred to as "dataflow editors", so I would literally have people murdering me if I had to introduce network delays to every action and could never let their happy path be lag-free. This is why I vehemently loathe HTMX and it's overbaked claims, it's a reduced user experience for anything with moderate complexity IMO.
If it would be valuable for you to see what I mean, I'm happy to create you a small React client with MobX stores & classes so you might start to play with these things. It is certainly a high-complexity process to create strong client capabilities like you and I discuss and describe, but now that I have them the act of making a new app is very much not complex, not any more than creating your servers is given that you have your libraries & capabilities of choice.
I would like to highlight that the above responses are more than uncharitable. They are somewhat condescending. It might be useful to practice a less confrontational style of online discourse.
No, that doesn't matter. If both users in the thread above still haven't realized that they're debating tabs vs spaces, they won't no matter what you tell them. Of course, tabs vs spaces debates always, 100% of the time, devolve into angrily shouting at each other, it's unavoidable.
> backend guy claiming clients can't dynamically handle auth by arguing server round-trips aren't ok for SPAs but totally for HTMX vs frontend guy saying not lifting as many interactions to the client makes for worse user experience
Interesting stance. Let me guess, you have never implemented a user interface?
No? I mean, I've been implementing user interfaces since Visual Basic (+/-) 5.0? What does that have to do with anything? I'm not going to argue with you guys, sorry. I'm talking meta - the way you talk between yourselves makes me want to scream and escape. Whether you're right or not really doesn't matter at all here.
EDIT: And obviously, I know I can just ignore you and move on, which is what I did. Notice that I didn't reply to your or your adversary posts, but to another person. That's mainly because I get nervous around delinquents - which is how you two sound to me...
> To me this is like building a house that can never be expanded,
Or alternatively it's a good application of YAGNI.
There's always a difficult balance between that as a principle and "do work upfront as it will take you longer to do later down the line"
Everyone makes this decision based on client budget, time constraints and a host of other factors. My clients tend to prefer to save money in the short term. That's not uncommon and it's a rational position to take for many.
> because if at some point you need to render on a client other than a browser
Been doing this for decades and this has cropped up so few times I can't actually think of any.
Your clients might not be my clients. Your projects differ from mine. Don't generalise from your own experience. There's a lot more websites than there are apps.
If you already have an app with no AJAX going on, HTMX is hugely helpful in progressively bringing it in. You just provide endpoints for the forms or other pieces of pages that need updating, and wire them up to the page with the right HTMX tags.
Even starting from scratch, it can be very productive to keep all your HTML in one place- eg Django templates- and use HTMX and a sprinkling of JS to provide client-side interaction. You can jump straight into laying your app out without building the whole thing up as platform-agnostic APIs first. When you need API endpoints you can start adding them based on the context objects you're providing your Django templates, since all the information is in there anyway. There's real benefits to putting all your presentation and most of the logic on the server.
And keep in mind that many web apps will never ever need to also be a native app.
I've used Intercooler.js (the predecessor to HTMX) a bit on top of Django and small Go apps (no framework). If you don't need a lot of complex state and interactivity on the front end it can work well. This approach avoids the complexity of having two codebases, you can get the performance/usability benefits of partial page updates while keeping 99% of the codebase in one language.
Depending on your architecture, an API or rendering HTML is an implementation detail, rather than the meat of the application. Have your business logic elsewhere and have the API or controller endpoints call out to those and just do the business of rendering JSON or HTML in the controllers.
(I think this is inversion of control, someone can correct me if I'm wrong)
can't speak for the others, because they are still stateless, but LiveView is stateful and this is the point, you're not constantly rocketing perfectly good content into the trash can; it's essentially a "content-aware cache layer":
This is a specific example of a class of problems I refer to as "Where the indirection go?" In this case, the indirection is in the form of a function that takes structured data and produces html. That function can execute in the server, or on the client. Which we can term moving a function "closer" or "further" from the client. This is easier to imagine with isomorphic javascript code, but it applies to anything (with an extra translation step as you cross the network boundary).
What you've discovered is a general property of systems that you want to keep your entropy low for as long as possible, and defer the final boost in entropy until the last possible minute. This keeps your options open. It also means publishing low entropy APIs and boosting in the client. In your case, you've correctly noted that it allows you to support different clients more easily.
There are 3 reasons to pay the price to boost entropy on the server: to intentionally make it harder to consume your API, to protect a proprietary boosting function, and because you didn't realize it was bad design.
Interesting way to frame the idea as entropy. Starting every project with a backend and SPA client seems like adding unnecessary entropy.
I'm glad we have middle-way options where we can progressively add dynamic functionality to rich-server, lower entropy applications and make an intentional decision whether to add a rich-client if the need arises.
>you have to start from the beginning and write an API now.
You could refactor your code to share mostly everything .
I have no idea how this templates system work though so maybe you need a different one.
so the API returns an array of users object as a json
the html side of things would have a render function that has as input the same array of user objects. What you will probably lose versus complex frameworks is magic stuff like 2 way data bindings but from my experience those always caused issue, I prefered the old react way of doing things, it was more "functional style" and less magic under the hood and I could(and used) JS and not a special template language with special keywords to render stuff.
Amway my point is yu can reuse the API code for the html render stuff too.
> Whether or not you love or hate this model of development is irrelevant, because if at some point you need to render on a client other than a browser -- you have to start from the beginning and write an API now.
That's not true generally since many mobile apps are simply wrappers around a WebView. Rails Turbolinks basically has mobile shims that allows you to deploy to all platforms.
Sure, if you can't use a Web View on your mobile app (idk, for performance reasons) that's something else. But that's not the rule.
I presume you can keep the logic code separated and being consumed by the UI generation on the server.
This way, if you only need the logic for another platform, you can just consume it directly. Perhaps you'll have an additional job to write the HTTP endpoints around it directly, but that's a quite simple job.
Take Rails Hotwire for example. It's easy to write, easy to add JS when you need it, easy to make mobile apps since they've already done the work. It's live on Basecamp and Hey.com apps, it works very nicely. Hey is easily the snappiest email client I've ever used.
If you render everything with JavaScript then HTMX is not really going to be useful at all. It's useful for developing apps that simply don't need the sort of complex client-side interaction that those frameworks enable. The article points this out- if you want to write a SPA in React, HTMX is not going to help at all. But if you want a mostly-server-rendered page with some AJAXy interactivity, HTMX is much much easier than trying to shoehorn in some client-side rendering just for the AJAXy bits.
The main thing is is that there is still a substantial demand for tools that let you mostly avoid writing lots of JavaScript, whether on the server or the client.
I feel like I gotta call out the LiveView is a bit in a class of its own. HEEx is rendered on the server, but the diff sent over the wire is the absolute minimal diff it can be with a map of where it in the DOM. Often this is just text. LiveView also gives you a running process on the server (a virtual machine process, not a linux process) that holds the user's state (like if they're logged in, for example) and makes things like "who's online?" and other concurrent features almost trivial.
I was writing web apps when rendering templated HTML views on your server was standard, and you had controller endpoints that returned HTML content, or things like Rails "Unobtrusive Javascript" where you had code like:
As an automatic callback to some action.Whether or not you love or hate this model of development is irrelevant, because if at some point you need to render on a client other than a browser -- you have to start from the beginning and write an API now.
IE, so that your Android/iOS app in Java/Swift or whatnot, can ask your backend for data and render it using the tools available on that platform.
When we turned apps into pure API's and just exchanged data with them, it opened up the ability to be platform-agnostic.
I guess I don't understand why someone would choose to build an app that sends HTML over the wire instead of JSON/XML/whatever, that any client can render. All it means is that you have to write this functionality later when you want it, in addition to now having this HTML stuff.