Designing Around Client Logic & Patterns of Web Applications

Learn common patterns used by web applications to manage state.

Designing around client logic

As a web application begins to act more like a desktop application, the application needs to maintain a lot of state and logic that only pertains to the user interface (UI). It doesn’t always make sense to manage the client-only information and logic on the server, so JavaScript applications in the browser became more and more complex.

JavaScript manages the interactions a user has access to, which may be harder to model as CRUD resources and actions. Single-page application JavaScript frameworks often have a different set of actions. As a result, these frameworks have structured themselves quite differently from server-side Rails applications. For example, a primary concern of JavaScript application frameworks is to manage the state of the client’s objects and data that is interacted with, such as which items are active, and the frameworks often favor having many relatively small constructs that manage the data and logic for a small part of the page.

Therefore, a problem for many client-side app frameworks is sharing common state information among otherwise unrelated small components across the page or the app.

On the server side, sharing a common state is not a concern in the same way. A server-side Rails app stores a global state in the database. It generally doesn’t worry about the mutability of individual instances because they don’t last beyond a single request. How, then, would we structure an application that combines server-side logic that is Rails-shaped with client-side interaction that is JavaScript-shaped?

Option 1

One option is to do as little in JavaScript as possible. In this paradigm, the server generates rendered HTML and the client manipulates the existing DOM. When the client wants to update all or part of the page, it makes requests to the server, receives HTML, and inserts that HTML directly into the DOM. This was more or less the original Web 2.0 paradigm. It was also the paradigm supported by early versions of Rails with helper methods. These made remote calls and inserted the returned HTML into a DOM element with a given ID.

In modern Rails, Hotwire and Turbo allow a more powerful version of this paradigm to create interactive client experiences while writing little to no JavaScript. Again, it helps that many client-side flourishes can now be done in CSS.

Option 2

On the other extreme, we have a single-page JavaScript app that does the maximum amount of work on the client. After sending the original JavaScript, the server is limited to sending data back and forth, usually with JSON. At the same time, the client converts that data to DOM elements using templates or JSX or the like. The client also manages the state of the application, including the address the browser displays in the address bar and the way the browser’s Back button works. The client is responsible for ensuring the server is informed of any data change that needs to be persisted.


Both of these options have their benefits and drawbacks. There’s also a middle ground where individual web pages might have their rich interactions, but we let the server handle the transition between pages.

Patterns of web applications

To make this architecture discussion more concrete, let’s look at how these decisions might play out in a specific web app. Slack is a real-time collaboration and chat application that runs in a browser. Later in the course, we’ll look at how an application might handle real-time chat notifications. For now, let’s focus on two user interactions:

  1. When users click in the sidebar to remain in chat but change the Slack channel they are looking at.

  2. When users click to view their profiles, it completely removes the chat interface and replaces it with something more like a form.

Switching channels

In the first interaction, the basic UI stays in place when the user clicks on a different channel, but all the displayed data changes. There are a few ways to handle this change. One option is to make it so that clicking on a channel triggers a page refresh, sends a normal HTTP request to the server, and redraws the entire page. However, this would likely cause the page to flicker and lead to poor user experience. The Turbo Drive in the Turbo library would prevent any flickering in the app. This is the solution that uses the least amount of JavaScript.

Get hands-on with 1000+ tech skills courses.