1. 15

TL;DR: I am a C++/ObjC guy with a basic knowledge of HTML/CSS/JS. I want to build a dynamic web-app (to be served on localhost) quickly. What client-side library can I use that will save me from doing everything by hand, but without having to learn & master a complicated framework like React or Elm?

I’m primarily a desktop/mobile developer, and in recent years I’ve focused on non-UI C++ library code, but I’m delving back into web dev for a side project of mine. This project is client-side, cross-platform, and needs UI, so the easiest approach is to drop a little HTTP server into it and serve up a website on localhost.

I’ve dabbled in web dev off and on since 1995, but never that seriously. I know HTML, CSS, JS, DOM, XHR … but basically as they were in 2008. I’ve never used the fancy frameworks the kids are into nowadays, like React or Elm. So the logic I’ve done so far has been by typing “document.getElementById(“button1”).onclick = function() {…”, they same way I did it in 2000.

I wonder if there are better ways to do this now, that don’t require a big learning curve or reading a 500-page book. On the View side I grabbed Bootstrap, which has really helped me put together a halfway-nice looking layout. But what about the M and C?

Ironically, the ubiquity of web-dev makes it hard to learn about this by googling, because the web is full of awful clickbaity content-farm sites about the Top 50 Bootstrap Themes, or The Best React Tutorial, or Teach Yourself CSS In Two Hours… So I thought I might get better advice from the fellow brainiacs here on lobste.rs.

Some random thoughts:

  • This is a localhost-only single-user site, so I don’t care about scalability, performance or auth. Bandwidth is infinite so building a SPA seems pointless.
  • It’s more or less a social-network app: posts, feeds, user profiles…
  • I’ve already cobbled together a basic C++ server-side framework with regex routes (cpp-httplib) and templates (inja).
  • I’m agnostic about putting logic on the “client” vs. “server” side; it’s the same CPU and the same programmer.
  • I probably want to use TypeScript, as I prefer fixing bugs at compile-time.
  • What do the cool kids do for server push nowadays? (Does just saying “server push” make me sound old?)
  1. 31

    Serve static html from the backend, and forego dynamic behavior on the front end. You’ll spend a lot less time doing things unrelated to your goal.

    1. 11

      This is the answer. Don’t mess around with SPAs, they will make your life much harder. Just write a traditional web app. Use express.js if you know JS, or sinatra if you know ruby, etc. These are tools you can actually start using in an hour or less, that can serve routes in a few lines of code, and whose features you can learn incrementally as needed. If you want to add a little slickness to your UI (transitions, fetches without page reloads, etc), while staying in the classic html/css paradigm you already know, have a look at https://htmx.org/.

      1. 2

        Use express.js if you know JS, or sinatra if you know ruby, etc

        It’s C++. There’s a lot of it already; the UI is just a layer on top. As I said I used a couple of libraries to give me a rudimentary routing system (based on path regexes) and Jinja-like templates.

        1. 2

          I have a lot of webdev experience with various stacks, but not C++. From my (limited) general experience with C++, I would imagine it would be a cumbersome web development experience, but if it’s working for you, go for it. I had assumed you were asking for something else. Either way, I think my original advice remains the same. Backend language you are familiar with, minimalist framework you can learn very quickly (or none at all, eg, in golang the std lib is enough), and htmx for UI effects/fanciness if needed.

          EDIT:

          From your other reply:

          And I wouldn’t be considering HTML for the UI at all if it couldn’t do dynamic behavior; that’s table stakes. It’s not 1995 anymore and my goal isn’t to rebuild Geocities :)

          It really depends how complex the dynamic parts need to be. At some point, if the frontend logic gets out of control, something like React may be warranted. But ime most sites that use React don’t need it. For a “social-networking”-esque site, I could see this going either way. But my instinct is still to start with a traditional architecture augmented by htmx until you find yourself needing more.

      2. 7

        The HTML has to be dynamically generated based on the (C++) model layer. This isn’t a static website. But I’m fairly satisfied with the C++ template engine I’m using.

        And I wouldn’t be considering HTML for the UI at all if it couldn’t do dynamic behavior; that’s table stakes. It’s not 1995 anymore and my goal isn’t to rebuild Geocities :)

        1. 6

          I have written… a significant amount of javascript in the past ~15 years.

          I would recommend you start by thinking carefully about where you want the state to live. Having some state in the javascript, some in the URL, some in HTTP cookies and some in the server (‘stateful connections’) is a recipe for trouble. IME, storing auth state in cookies and everything else in the URL has consistently been the most compatible with how browsers want pages to behave, at the cost of restricting how much state you can store (to a few kb).

          For most content, it’s easier to get a better result if you use <a href links rather than javascript (generating different HTML in the server process based on the URL).

          For parts which constantly receive new input from the server (eg a chat window), you can render an iframe to a URL which holds the connection open (using chunked encoding) and sends new content as it arrives. This requires very little code, is fast, and retains state correctly across page refreshes with no action on your part.

          Beyond that, a very small amount of javascript to toggle classes is typically enough.

          1. 1

            Thanks. Fortunately there is pretty much no UI-specific state I need to keep. It’s all running on a client device so there’s by definition only a single user.

          2. 5

            Here’s a few fairly powerful vanilla techniques:

            • Replacement for onclick: document.querySelectorAll(...).addEventListener('click', function(event) { const element = this; ... }
            • Dynamically fetch HTML rendered by the server: fetch("/url...", { headers: { 'Content-Type': 'text/html' }, ... }).then(resp => resp.text()).then(htmlText => { element.innerHTML = htmlText; })
        2. 12

          If I were you I’d consider investigating htmx. The premise is that instead of writing your UI in JavaScript, you deliver plain ol’ HTML from the server and htmx will help you bridge the gap between static HTML and a fully interactive application

          Some things htmx can help you with that you’d otherwise use a front end framework for:

          • Triggering arbitrary requests from HTML events rather than just GET and POST
          • Changing client-side state, such as toggling CSS classes
          • Incrementally loading new page content from the server (ie instead of triggering a page reload)

          (Basically it’s a framework that abstracts away a lot of the work you are doing right now)

          TBH I haven’t had time to dig into it myself, but I’ve heard a lot of good stuff from people I hold in very high esteem, so I’m recommending it anyway. If you do try it out I’d be very curious to hear your take on it!

          1. 3

            We may have a winner! HTMX looks very nice, and along exactly the same lines as the very limited interactivity I’ve already built by hand. Thanks!

          2. 7

            I’d recommend something like Vue. Yes, it is a framework kind of like React in that there will be a bit of a learning curve to learn the framework, but compared to React I personally feel Vue feels a lot more like native JS in how you approach it than React does and is a lot easier for a beginner (in JS) to understand.

            Ultimately though SPA’s are tough and there’s a good reason that people use frameworks. It might be worth it to learn the basics of at least one. I think Vue, considering your experience with JS, is going to be the smallest hill to walk up.

            1. 2

              I add my vote to just going static (at least at the beginning), but if you want spa, Vue is really nice. While you can use React and others ad-hoc, the usual tutorial will start with installing node, babel, lots of other things. With vuejs it’s really easy to just yolo add it using a standard script tag and you’re ready to use it on that page.

              I’m happily using it that way with no build steps in the projects where I need some interactivity.

            2. 5

              the logic I’ve done so far has been by typing “document.getElementById(“button1”).onclick = function() {…”, they same way I did it in 2000.

              This does still work; if your UI is simple enough vanilla JS is probably fine. Things worth knowing about since 2008:

              • A lot of the good stuff from jquery is built-in to browsers now, e.g. document.querySelector. and fetch for a more ergonomic alternative to XHR.
              • Websockets are probably the right foundation for server push these days.

              If it starts getting harder to maintain, learning a framework may be worth it, but keeping it simple at first seems reasonable.

              1. 5

                I will third the htmx suggestion. You write almost no javascript that way, and it’s a natural fit for your server side templates. Essentially, to get partial page reloads, you will set up some of your “server” endpoints to return partial templates and you’ll use attributes on links and forms to designate which parts of the page those will replace.

                The near zero server-integration and the lack of new javascript you need to write is what makes it sound like a fit to me.

                And if you do want to write some stuff that runs in the browser, I suspect you will smile from looking at the same author’s hyperscript stuff. You may or may not want to use it, but hypertalk in the browser has to make you smile.

                1. 4

                  Given your stated requirements, I would look at Svelte or Vue 3. Both support TypeScript and both use a simple templating language that look a lot more like HTML than React’s JSX does. Since you already have routing set up server-side, I would avoid using those frameworks’ client-side routing features unless you’re sure you need them.

                  I’ve used Vue professionally now almost since the first stable release in 2015 and embedded interactive apps written with it into a variety of environments. I’m pretty happy with it. I think React would be fine too if you’re willing to learn JSX and a little more jargon (“hooks,” “slices,” etc.). My only reason suggesting Svelte is that it’s faster than both of the other two, its API is leaner, and it isn’t carrying around the baggage typical of a popular incumbent framework. In Vue’s case, that baggage comes in the form of a fractured ecosystem, much of which is still on Vue 2. I get the feeling that you’d rather work with a framework that’s operating on a clean slate than learn about all the baggage.

                  For server push, there are a couple of options you might look into. The first is server-sent events. The client implementation is very straightforward. I’m not sure the best way to implement the server side in C++, though. Another option to consider if you’re open to redoing your web server is Phoenix, a web framework for the Elixir language written on Erlang’s BEAM VM, and its Live View feature, which allows you to define your data bindings declaratively and leave the dynamic updates to the framework via web sockets. I’ve dabbled in Elixir and love it. I know some folks who use it in production. Updates via web sockets can have some scaling issues, but it doesn’t sound like you’re anticipating a lot of traffic since you’re just running your server locally.

                  To the other commenters, claiming that interactive behavior is a not a valid need is not helpful. Also, writing lots of data binding with vanilla JS turns into spaghetti code, which is probably why the author is asking for a better solution. Better solutions do exist and it is fortunate that they do, regardless of whether they fit preconceptions about the right way to build web software.

                  1. 2

                    For server push, there are a couple of options you might look into. The first is server-sent events. The client implementation is very straightforward. I’m not sure the best way to implement the server side in C++, though. Another option to consider if you’re open to redoing your web server is Phoenix, a web framework for the Elixir language written on Erlang’s BEAM VM, and its Live View feature, which allows you to define your data bindings declaratively and leave the dynamic updates to the framework via web sockets. I’ve dabbled in Elixir and love it. I know some folks who use it in production. Updates via web sockets can have some scaling issues, but it doesn’t sound like you’re anticipating a lot of traffic since you’re just running your server locally.

                    To add onto this, raw Websockets will let you server push as well, but they can be a bit annoying to use. Server-Sent Events are the way to go if you’re not anticipating a lot of server-started chatter.

                    1. 1

                      Thanks! I’ve been going through the Svelte tutorial and enjoying it. It doesn’t show how to integrate with back-end data, but I assume I can serve up HTML templates and call XHR from Svelte.

                      1. 2

                        Yes, XHR or the fetch() API will definitely work with any of those frameworks. They’re a bit low-level, not following redirects and, in the case of fetch, resolving rather than rejecting promises when the response is a 5xx error. I’ve found ky to be a decent TypeScript abstraction.

                    2. 3

                      Think architecturally about the problem. What are the quality attributes that are driving you toward attempting to choose a front-end framework? It’s completely OK if you explicitly want to learn a front-end framework as an exercise in personal enrichment or as a résumé building activity, or want to experience the joys and pains of the modern (false? needless?) dichotomy of front-end and back-end. A lot of backend-focused web frameworks have HTML generation baked in with the premise that they’re rendering instead of relying on the browser to load data and then render it. It’s a bit cheeky to recommend Vanilla.js but learning modern JavaScript for progressive enhancement is probably a big first step in the right direction.

                      If you want your backend never to be concerned with HTML, then pick a framework based on your other principles: something type-safe and easily shipped. I know very little Vue but took over for a tiny Vue codebase and it’s been okay. WASM might be a great target if you want to stay in your current toolset and it can do what you want it to do. But remember that if you go down this path, you’re really building a true client+server application. The client is just a browser program and the server should never be concerned with presentation.

                      1. 3

                        If you need JS and don’t want a framework, vanilla JS is pretty good these days.

                        If you’re doing something vaguely hypertextish then htmx might be cool.

                        If you don’t need JS don’t bother, use something normal and comfortable for you. C++ or ObjC web framework doesn’t sound like my preference, but I’m sure there are a couple if you like. Lots in rust of course, probably for swift too, whatever feels worth it to pick up to you.

                        1. 3

                          I’m curious why you say you want a client-side library but don’t want a SPA. Also why you call React a complicated “framework” (it’s a library, and it’s definitely the least complicated out of the major players by far - you can complicate it as much as you want, but you don’t need to at all).

                          You don’t need “MVC” for this project at all. You can also do this stuff with vanilla JS but it’s not going to be any easier than using React if you want to use real modern JS / DOM methods.

                          TypeScript is only going to get you bugfixes for typing issues which isn not likely to be an issue for you, especially since you’re making something that is so small.

                          I would recommend writing this on the server and not using JS at all. If you really feel like you need to use JS, you’re either going to want to learn about ES modules + modern DOM or just go with React.

                          FWIW, here is the basic gist of a React-style list of messages with a button that calls some server:

                          // Button.js
                          
                          const handleClick = async () => {
                            // hit some backend server
                            const response = await fetch('/something');
                           
                            // now you can do whatever you want with the result
                            console.log(response);
                          };
                          
                          const Button = () => {
                            return <button type="button" onClick={ handleClick }>Do something</button>;
                          };
                          
                          export { Button };
                          
                          
                          //-----------------------------------------
                          
                          // Message.js
                          import { Button } from './Button';
                          
                          const Message = ({ text }) => {
                            return <p>{ text } - <Button /></p>;
                          };
                          
                          export { Message };
                          
                          
                          //-----------------------------------------
                          
                          // MessageList.js
                          import { useEffect, useState } from 'react';
                          // assuming you slap all of these in one components folder 
                          // (not best practice, but it won't matter on a small page)
                          import { Message } from './Message'; 
                          
                          const MessageList = () => {
                            // initialize the local state here with an empty list
                            const [ messages, setMessages ] = useState([]); 
                          
                            const getMessages = async () => {
                              try {
                                setMessages(await fetch('/messages'));
                              } catch (err) {
                                console.error(err);
                              }
                            }
                            
                            // grab new messages when this component is rendered
                            useEffect(() => {
                              getMessages();
                            }, []);
                          
                            return (
                              <ol>
                                { messages.map(message => ( <Message text={ message } /> ))  }
                              </ol>
                            );
                          }
                          
                          export { MessageList };
                          
                          //-----------------------------------------
                          
                          // App.js (this is setup for you with create-react-app
                          // other imports here
                          import { MessageList } from './components/MessageList';
                          
                          const App = () => {
                            return <MessageList />;
                          };
                          
                          // any other CRA autogenerated stuff
                          

                          I did this in this comment box so there could be an error here or there. Also keep in mind that there are different ways to do these things. You could render the message text as a child of Message instead of using a prop. You could export the Components as default. If you want to do this with testing, you’ll likely want to put each component inside of its own folder (named the same) and add a test for it in the same folder. You might want global state at some point, which you can do with passing props, using providers, using vanilla JS event-based pub/sub, or even getting into more complicated libraries for state management (I would avoid Redux, go with something simple like zustand if you really want to try this).

                          Good luck.

                          1. 1

                            I’m curious why you say you want a client-side library but don’t want a SPA.

                            SPAs seem like a tradeoff that complicates your code in order to reduce latency and allow offline use. Whereas my all-local system already has near-zero latency and works offline.

                            Also why you call React a complicated “framework” (it’s a library, and it’s definitely the least complicated out of the major players by far

                            A framework (IMO) is something you fit your code into, i.e. it tends to dictate how you structure your code and it usually ends up calling you. Whereas a library is something you call. Admittedly this gets imprecise in the context of a browser, which is itself a framework, and where there can be many layers.

                            Having not used React, I think I was influenced by reading various people’s negative opinions of its complexity. I freely admit I don’t know what I’m talking about! I did start reading docs for React Native over the weekend, and it looks like a very good way to grow up into actual native apps someday.

                            1. 3

                              React doesn’t dictate any structure around your code. You could just as easily call it using the underlying functions behind the JSX if you hated JSX for whatever reason. FWIW react-native is a bit more complex than regular react (because of the native part). Regular react can be even simpler than the example I gave above.

                              I would be extremely careful about listening to what others say about any library (including me!) and try it out yourself. You can mess around with any JS stuff in codesandbox.com without having to add anything to your own system.

                              1. 2

                                I’d toss in another vote for React! if your app is going to be very small, then it’s pretty easy to integrate it, as you can directly include the react libraries on your page and call them directly: https://reactjs.org/docs/react-without-jsx.html

                                however, if you feel you need routing (e.g. if the user clicks on /settings, show them a settings page without actually refreshing the page), then you may find yourself with more restrictions, as your application is no longer just rendering HTML to a specific div but also managing history and intercepting user navigation.

                            2. 2

                              It would probably help to understand what features you think should be done with JavaScript and why. Saying it’s a social tool gives me ideas, but there’s a range. A project like lobsters seems suitable for server side rendering with just snippets of client side JavaScript, Vanilla or otherwise. But if you have features in mind that require more complex interactions, perhaps you need more tooling.

                              P.S. despite this feedback, I think this is an unusually clearly stated “what framework do I use?” type question. You’ve given us way more context than most people who ask something similar give.

                              1. 2

                                So far, the only dynamic feature I’ve implemented is making the New Post… button display a new-post form, and then submitting that form and getting back the new post’s HTML to splice into the DOM.

                                Other instances will be progressive disclosure of lengthy posts and of comments, an Edit button for your existing posts, buttons to change the sort order and filter…

                                1. 2

                                  This definitely feels like a case where “sprinkles” of JS would be appropriate. htmx also sounds very interesting, but I’m not familiar with it. Stimulus also sounded interesting for adding interactivity to server side rendered html in a principled and structured way, based on a podcast I listened to.

                                  A framework like Vue or React seems like more trouble than it’s worth in this circumstance.

                                  P.S. I’m gravitating towards being a backend developer now, but I got my start doing front-end work in the backbone days, and have used React a little bit.

                              2. 2

                                Since you already know C++, why not build the UI in something like Qt?

                                I’ve been a frontend developer for most of my career. I wouldn’t recommend spending much time learning the Web stack if you already know something else that can do the job just as well. Learn it if you feel like learning something new, otherwise just use a UI framework built with C++.

                                1. 0

                                  I’ve used some desktop apps that were written with Qt and didn’t like the UI at all. (Sorry, I’m a Mac snob, worked many years at Apple.)

                                  1. 2

                                    I also happen to be a Mac snob and prefer a native Cocoa app over anything else :)

                                    That said, Qt apps can absolutely look and feel great on macOS. If you use the default Qt widgets that try to emulate the native macOS look, you’ll end up with an uncanny valley effect. The solution is to simply give up on looking native – the same thing you’d do when building an Electron app – and create fully custom styles for your widgets. Depending on how complex your app is, this might be easier than learning an entirely new tech stack.

                                    Telegram Desktop takes this approach, and it’s one of the best desktop apps I’ve used. And it’s open-source! https://github.com/telegramdesktop/tdesktop.

                                    Ableton Live takes this approach, too. They seem to have built an entirely new set of widgets so that you can’t even tell the app uses Qt at all.

                                    1. 2

                                      As a fellow snob, why not native? If it’s because cross-platform, you can always do native for the platforms you can support well and non-native for the ones you don’t.

                                      1. 1

                                        If I were more up to speed on Apple GUI development I’d probably have just gone with that. But it’s been about eight years since I did anything nontrivial, and the ecosystem is so different now. SwiftUI looks good, but I hear people say it’s still not ready for prime time. So I thought it might be easier to hack together a provisional cross-platform UI at first.

                                  2. 2

                                    I personally would do server side rendering via Go, but if you’re doing C++, just use your server-side framework and do all you rendering server side. For any client side dynamic behavior I personally recommend progressive enhancement, doing your JavaScript like in the 2000s is not bad if you keep the codebase small and the complexity low.

                                    1. 2

                                      As others have said, doing most templating on the server is probably the best option if you don’t want to use a big framework and it’s becoming popular again these days. Doing document.getElementById(“button1”).onclick = function() {… also still works and the browser APIs have improved a lot over the years.

                                      Some pointers:

                                      I probably want to use TypeScript, as I prefer fixing bugs at compile-time.

                                      You can easily use TypeScript without necessarily using a framework or any of the Node tools, just install tsc, generate a configuration file using tsc --init and set it to output files to public/ or something similar. There’s a --watch flag which automatically recompiles files that have changed which is nice when developing interactively. If you don’t use modules (import/export/require) or you just use type imports (import type { ... } from '...') you can forego using a bundler and all the complexity that comes with it.

                                      What do the cool kids do for server push nowadays?

                                      I think the cool kids use websockets, but those bring in a ton of issues. I’m not very familiar with server-sent events but they look like a valid solution. Personally, I’d just use polling or chunked transfer encoding if I was feeling fancy.

                                      XHR

                                      The Fetch API is much nicer than raw XHR and a lot of web APIs use promises these days so you should probably look into those.

                                      1. 1

                                        Also, regarding Bootstrap, I highly don’t recommend it, personally I would go with something like https://github.com/oxalorg/sakura, https://watercss.kognise.dev/ or https://newcss.net/, or https://picocss.com/ for more complex UIs (you can see it in action at http://twtxt.net/ for instance) or any other classless CSS framework, even tho I prefer no framework.

                                        1. 1
                                          1. I wouldn’t be afraid to learn a JS framework. Just like any new paradigm you learn, the ideas end up being applicable even when you aren’t using the framework. React is sort of a bear to learn because it is insufficiently opinionated to make an app, but Vue and Svelte are good. If time is a factor, maybe you’ll want to hold off on this for now, but it’s definitely helpful to have them in your backpocket later.

                                          2. HTMX is a good minimal JS solution, but you will hit the walls pretty quickly if you want anything even slightly complex.

                                          3. I like Alpine.js as my JS microframework because it integrates well with server rendered templates and adds just enough reactivity to smooth out working with the DOM. Similar microframeworks to look at are Stimulus.js/HotWired (from the Rails team) and Petite Vue (from the creator of Vue).

                                          1. 1

                                            I have some boilerplate code that allows to control the frontend from the backend without the need to write a line of frontend code. Here is an example REPL-like app, see the mockup to have an idea of how it is rendered. The kernel frontend side is in on.message, the other side of tube, the server is the websocket coroutine. It just works.

                                            Did you consider emscripten?

                                            1. 1

                                              If you want to stay on the c++ side, Qt (Qml and Widgets) compiles to web assembly. Have used it for a few demo’s and it works well enough.

                                              1. 1

                                                I’d use sth like this: https://github.com/fpereiro/gotoB or vanillajs

                                                1. 1

                                                  Just rewrite using ruby on rails (actionwire for server push) or django (channels+signals for server push). You’re welcome.

                                                  1. 4

                                                    Did you actually read any of what I wrote? This is a client-side native app I’m adding an initial UI onto, not a web server.

                                                    1. 1

                                                      I want to build a dynamic web-app (to be served on localhost) quickly.

                                                      I guess I’m seeing things that aren’t there or dynamic web app to be served on localhost doesn’t mean what it means.

                                                      1. 2

                                                        Re-reading my post, it’s not as detailed as I thought it was. Sorry. This is an existing project I’ve been working on for months with something like 5-1000 LOC of C++ (plus 3rd party libraries.) One of the key reasons for doing it in native code is that the closest existing software — Scuttlebutt — is written in JS and is dreadfully slow and resource-hungry.

                                                        1. 2

                                                          Okay you have C++ code that manages data structures that are distributed to multiple computers (I guess that’s what scuttlebutt does?) and you want to visualize it on a local web app. Hope I got it right this time. And I’ve been here.

                                                          I had a Qt application which used the Qt state machine framework to store many state machines.

                                                          I had a separate websocket app written in python using async/await (but it could as well be nodejs) for visualization that connected to Redis.

                                                          The C++ application used PUBLISH to send live events to Redis and PUT to save its current state.

                                                          On the web app, I did SUBSCRIBE to redis events to listen to live updates and used REDIS GET to initially download the state (sent via websocket on initial user connection). The events were then pushed to the websocket clients.

                                                          On the client side, websocket events were parsed as JSON in the form of {"function_name": "state_change", "kwargs": {"from_state": 1, "to_state": 2}} and the lib[rpc.function_name](rpc.kwargs) function was called which did its animation using d3.js

                                                          Redis helped me decouple C++ from the web server and allowed me to distribute the state to multiple websocket servers without doing much thinking. It also reduced the connection and messaging to a few lines of code. It also provided a storage mechanism where I could keep the initial state to send to websocket clients.