Tom Hummel

Four Ways to Build Web Apps

Intro

This is my opinionated list of four approaches to building websites and web applications. Publicly hosted on the internet, serving HTML, CSS, JavaScript, images, etc over HTTP.

#1: Hugo Static Sites + Progressive Web Apps

Static websites are boring. Vendors rarely talk about them because the margins are miniscule compared to flashy, compute-heavy services. It is seen as a table stakes offering. Though they have received more attention during the “JAM Stack” trend, my position is that they are still underappreciated and underutilized.

Static hosting has near-zero ongoing operational burden, is free to host at low to medium traffic levels, plays well with CDNs, and has well established patterns for updating content. If your project can get away with being a static website don’t mess it up by doing something more complex and expensive. Choose a tool (such as Hugo) and learn every facet of how it works. You’ll be confident of when you’ve outgrown it and need to consider a more complicated/expensive approach.

Further, every static site can be installable on mobile phones just like a native app using Progressive Web App (PWA) capabilities and made accessible offline using service workers. Further, you can layer interactive functionality and privacy-respecting persistence (see: Nomie by Brandon Corbin), all without a traditional server backend. PWA APIs have excellent support on mobile web browsers and getting better all the time. Do it.

Examples

#2: Cloudflare Workers

Cloudflare Workers is a serverless (yes, there are servers but you don’t have to manage them), edge compute platform. It is very easy to get started building standalone web apps that would traditionally require a server.

Cloudflare Workers can be standalone apps or you can stitch worker functionality into your static website using Cloudflare Pages Functions. For the latter case you add a /functions directory to the root of your Cloudflare Pages git repo and functions are automatically deployed alongside your static site. A script at /functions/form-submission.js would be deployed at the url path: /form-submission/

Examples

Graduating from Options 1 and 2

A few notes before presenting Options 3 and 4. If you’ve outgrown Options 1 and 2 it could mean you need to run your own linux container or server. However, if you can avoid the overhead of this by using Options 1 or 2 it is absolutely recommended. Options 1 and 2 are free to run at low usage levels and scale elastically as demand rises. Managing change is fairly simple and maintenance overhead is minimal. Seize on these benefits if you can. Options 3 and 4 offer approximately the same levels of complexity with different tradeoffs on cost and capability.

#3: Linux Server Singleton

Option #3 is to run exactly one linux webserver to host your web app. Choose your application runtime (node.js, ruby, go, python), operating system, supporting agent programs. Store primary application data in SQLite and take advantage of its vibrant ecosystem. Committing to a single, all-inclusive server closes off complexity related to managing distributed systems - things like load balancing, clustering, discovery, tracing, and external databases. With fewer network hops to serve a single request, you limit the possible failure modes. With this approach, you can squeeze out fairly good latency and throughput for 80% usecases if you choose your path carefully.

Note: With a single server off the shelf, the most notable concern is lack of data replication and thus risk of data loss. The strategy explicitly includes approaches for getting application and operational state off the server to persistent stores in real time (litestream, fluentbit, prometheus or comparable - or SaaS providers like honeycomb or datadog).

Examples

#4: Containers on Platform as a Service (PaaS)

Option #4 is to choose a PaaS provider to host your application as a docker container.

Note: Option #4 does not include using managed kubernetes platforms like EKS, AKS, GKE, or OKE. These “managed” offerings come with significant operational costs and should be considered hostile to the goal of controlling complexity.

Examples

Beyond the Four Ways

The four approaches above attempt to control cost and complexity, provide a constrained set of capabilities in order to achieve a goal. These will cover the common 80% of usecases for web applications. When you outgrow these four it means you may have special requirements. At a certain scale an organization can benefit from investments in platform capabilities, such as those provided by Nomad or Kubernetes. The breakeven point on these investments can be much further away than you think but still may make sense. YMMV.

Aside: Complexity is Conserved … Until it isn’t

There is no such thing as a free lunch (TINSTAAFL). Complexity is conserved, meaning complexity cannot be removed it can only be moved around. For example, you can pay a provider to handle a class of complexity for you or bear those yourself - someone is doing it.

The interesting areas are where the complexity-cost tradeoff gets bent by innovation or commoditization. For example, nearly free static website hosting removed the need to run an apache or nginx webserver for these usecases. Cloud providers commoditized this capability and we all enjoy the benefit. Consumers moved complexity to the provider and cost dropped at the same time. These are the areas to be on the lookout for.

Aside: Advocacy for IaaS Identity and Networking

IaaS Identity and Networking features are underappreciated. They are often free or very low cost and enable elegant, secure, and simple solutions to common usecases. Do not miss out on these features by convincing yourself it would lock you into the platform. Commit to a provider and squeeze every drop of value from your learning investment.

Essential Features:

Which providers are worth considering for all-in investment? In my opinion, the short list is: Amazon Web Services, Microsoft Azure, and Oracle Cloud Infrastructure.

Conclusion

Edit 2023-07-04: This post got alot of attention. See follow-up analysis of this post.