Front-End Separation And The Irrational Love For Curly Braces 🔊

API, Front-End, JSON, HTML and browser rendering.

The picture of a red mustache that’s really really fancy. Don’t believe me? Here’s the original picture.

There’s a strong movement these days to build Single Page Applications (SPAs). The aim of a Single Page Application is to avoid full web page reloads and render parts of the page dynamically. By doing that, it will create a better user experience that feels like a native app.

In theory, the idea is great. But in practice, the execution tends to be a failure.

One common pattern for Single Page Application is to create separately deployable projects. The Server API Back-End (let’s call it the API) and the Client Front-End User Interface (let’s call it the Front-End).

The Front-End will request data to the API through HTTP. The API will return JSON in the response. The response will be parsed into JavaScript Object and Array Literals. The parsed response will be used as an input to Front-End components that build the User Interface.

Here’s one implementation of what’s commonly called “Single Page Application”:

The runnable code showing the communication between API and Front-End to render a Todo List. The "user details" is a parsed JSON response from the API containing 2 properties called "given name" and “surname” with the values "John" and "Doe", respectively. The "parsed Todo list" response has a property called "list." It contains an Array of Object Literals representing each item of the list with a boolean flag called "is done."

There are many problems with this approach.

Imagine you decide to change the API so that the properties from the "user details" response are renamed to firstName and lastName. The givenName and surname properties will become obsolete, but you can’t remove them from the response without breaking the Front-End.

If you do, in order to prevent the system to show an intermediary broken state, you’ll have to deploy:

  1. The API with the new properties.
  2. The Front-End to read from them.
  3. The API to remove the old properties.

All in that specific order!

The diff showing the "given name" and "surname" properties being renamed to "first name" and "last name." The Front-End will require changes and a separate deployment, otherwise, the system will be in a broken state, even if for a couple of minutes, until both API and Front-End finish being deployed in order.
If the Front-End is deployed separately from the API, changes to the API structure can break the Front-End. It will also require each component to be deployed separately, in a specific order.

Things become worse if tasks are owned by different people responsible for developing the API or Front-end. The communication overhead to agree on API contracts will consume time. It has a high risk of causing integration problems.

Also, if the same functionality of the API is consumed by more than one Front-End, like a Mobile App or another service, all of them will have to duplicate the concatenation logic for the givenName and surname in the header. If there’s a requirement to change the full name to use dashes instead of spaces, all Front-Ends will have to change their code.

In the social side of it, any public-facing modification to the API might start to require approvals. Bureaucracy will be stronger if the API is consumed by third-party developers. Over time, the API will start to become so rigid to a point where only additions will be feasible and removals impossible.

The more consumers, the harder it will be to change it.

Over time, an API designed like this will become very rigid as logic will start to get duplicated in the Front-End.

One might think it's not hard to remove the responsibility to concatenate the full name from the Front-End. To do that, just return the fullName property from the API instead of firstName and lastName. This way, the Front-End will just map the property to the User Interface, it won't have to write code for the concatenation logic. Requirements to change the rule will be contained in a Single Source of Truth: the API.

The diff showing the "first name" and "last name" properties being renamed to "full name". The Front-End has to change the lookup to the new property, but now if you want to change the concatenation logic you don't need to touch the Front-End, just change the value of the "full name" property.

However, the API can still rename fullName to name. If that happens, it will be a breaking change. The Front-End still expect those properties to be there.

How can you fix that?

The circumstance where a rename like this can happen is when the domain-specific definition for the "name concatenation" has changed. In this case, the definition can be modified to fullName, name, completeName, userName, wholeName… etc.

Those domain-specific definitions can change pretty often. If they do, it’s natural to try to keep the API in sync.

In order to avoid the Front-End from breaking if the domain-specific definitions change and still allow the API to make sense in a backward compatible manner, the domain model should be decoupled from the User Interface.

In order to do that, you have to start naming the properties to how they’re being used, not what they mean for the product.

In the case of the previous example, name the property to header, instead of fullName:

The diff showing the "user details" page being merged to a "todo list page". The "full name" property has been renamed to "header". The Front-End has to change the lookup to the new property, but now it's less likely it's gonna be renamed due to domain-specific definition changes.

This way, if fullName changes to name, that will be a variable/class change internal to the API Server. The Front-End only cares about the contents of the header. If the header value changes, it will automatically trigger an update to the User Interface without a separate deployment.

Over time, you'll notice that cross-changes between API and Front-End won't be needed that often.

If you name the properties of your API to how it’s being used on the page, then it will be less likely for the Front-End to change.

Up until now, I’ve just talked about problems and solutions for the header. However, the same problem exists for a Todo item that has been marked as "done". The state is stored on the Server and the API exposes it using the isDone boolean flag.

Instead of using a domain-specific definition called isDone, use the term striked that is User Interface specific. It communicates to the Front-End a message that the item has been "striked out." The "why" is an implementation detail of the Server exposing the API:

The diff showing the boolean property "is done" being renamed to "striked". The change was applied for each of the Todo items coming from the “todo list page response”. The Front-End has to change the lookup to the new property, but now it’s less likely the flag is gonna be renamed due to domain-specific definition changes.

Hmm… Something is happening here…

To reduce the long-term effort and increase the robustness of your API, you’ve started moving most of the logic out of the Front-End. However, the Front-End still has a lot of JavaScript code responsible to render the page, so renaming the header or the striked property to something else will still break the Front-End.

That's still too fragile.

Let’s rename the API response so that the format becomes even closer to how it's being rendered by the browser:

The runnable code for the Todo list page. The "user details" and "todo list" responses have been merged into a single "todo list page" response. The "list" of Todos has been renamed to an "unordered list." The "header" has been renamed to a "first level header" (h1).

Hmm… now the JSON looks very similar to the HTML…

Oh wait… Holy shit!

It is HTML!

What you’ve been doing so far is to read JSON and write client-side code to render HTML. That was done to work around the fact the browser can’t render JSON!

However, the browser understands how to render HTML.

If that's so, why don't you just return HTML in the first place? Let the browser render it for you!

The browser doesn't understand JSON!

The “Front-End” is already developed for you by Chrome, Firefox, Edge, and other browsers. They can probably do a better job to render HTML than you can.

Ok, let’s return HTML instead of JSON for your Single Page Application and see what that looks like:

The runnable code for a Todo list being returned from the API Server as HTML. The list uses the "unordered list" tag and the header uses the "first level header" tag (h1). The JavaScript code for the "Client Front-End Project" doesn't exist anymore, it's the browser. There's still some JavaScript to fetch the HTML from the server and dynamically render it as a fragment of the page.

Now it’s time to stop calling the thing as an "API" and start calling it as a Web Server because that’s what you're trying to build. The point here is that many people love to try to prematurely build a “generic API” for something that can be a simple Web Server instead. The Web Server can encapsulate the domain model internally and return HTML as the format for the message mechanism to communicate with browsers.

If you keep using this approach for Single Page Applications, you'll still have to write client-side code in JavaScript. However, you'll only have to write code to tell the browser where to render the HTML fragments returned from the Web Server. You won't need to write code to interpret another data structure and render it manually anymore. The browser will do it for you.

If you want to mutate the DOM without reloading the page to handle real-time actions – say to drag the position of an item from the Todo list using the mouse – then you can use client-side JavaScript to improve the user experience.

[…] The reason full-page refreshes often feel slow is not so much because the browser has to process a bunch of HTML sent from a server. Browsers are really good and really fast at that. And in most cases, the fact that an HTML payload tends to be larger than a JSON payload doesn’t matter either […]
DHH on the post "A modest JavaScript framework for the HTML you already have"

Nothing prevents your application to store client-side state. However, use it sparingly. Don't store state in the browser if you don't need to. Unless you have an incredibly strong use case, prefer storing state in the server and refreshing parts of the HTML instead.

If you use this approach, the questions will be:

  • Which fragments of the page should be dynamically loaded?
  • Which encapsulated components should have their own local state stored in the browser?

If you return HTML and want to change how the User Interface renders the header, remove it from the h1 tag and place it somewhere else, like a footer. This way the Front-End will never break because you’ll never have to change any JavaScript code to render something differently.

The diff showing the content of the "first level header" tag (h1) being moved to a "footer" tag. This change won't break anything and won't require multiple deployments in a specific order. The best thing is that the web already supports it by default.

If you return JSON from the Server and write a lot of JavaScript code to re-map that to HTML in the Front-End, then you're probably wasting your time.

Code that doesn’t exist is the code you don’t need to debug.

Don't waste your time to recreate the HTML you already have.
But… isn’t that just "server-side rendering"?

That's what it is! However, the term is misleading.

There’s no such thing as “server-side” rendering. The browser renders the User Interface, not the server. The only thing the server does is to send a text payload.

Just like JSON, HTML can be seen as an Application Programming Interface, but the browser is the one who downloads and codes against its format. The difference is that the browser can render it without anyone having to write any additional code. There’s no business value to download JSON data and recreate the HTML for the browser.

I recommend to refrain from using the term “render” with “server”, it makes no sense.

There's no "server-side" rendering. The browser renders the User Interface, not the server.
But… Facebook, Google, Amazon, NASA, Chuck Norris, Chewbacca, and the Pope are all using JSON for Single Page Applications!

I've talked before about Argument from authority. Just because a big company does something, that doesn't mean it's always the better solution for a general problem.

There’s one secret big companies don’t tell you. They have more teams working in internal tooling than you probably have in your whole company!

Maybe that works for them, but it will probably not work for you.

They don’t have the economic motivations to work out how to write better code with lower cost. They have a lot of money to pump into their internal tools and pay thousands of developers.

You don't. Stop copying them.

Just because a big company does something, that doesn't mean it's always the best solution.
But… JSON is very popular in the web community and there's a lot of blog posts!

There's this thing called Argument ad populum. Just because everybody does it, that doesn't mean it's always the better solution.

Uncle Bob speculates every five years the number of developers double. That means at any point in time half of the "community" will have less than 5 years of experience.

With that in mind, it's not a smart strategy to do something just because most people "in the community" are doing it.

Just because "the community" is doing it, that doesn't mean it's always the better solution.
But… this only works for simple read-only HTML!

The code examples presented here are maps. They are supposed to demonstrate the core idea with the least amount of noise. Unfortunately, maps have limitations. For that reason, I deliberately omitted how to do updates to the server, but I'll try to summarize it here:

  • You can easily submit a form with manually filled fields, prevent the default submission client-side and return the updated HTML fragment from the Web Server to update the User Interface dynamically. The server can be responsible to return the same fields pre-filled, no work is needed in the client. This way, the logic will be encapsulated in the server.
  • When the user clicks on an anchor link to navigate, you can prevent the default behavior client-side and return the target HTML fragment from the Web Server. The User Interface will be updated dynamically without a full page reload. The URL can be updated using HTML 5 History API, this way the page is still bookmarkable.
Any action that requires a page reload can be intercepted with JavaScript on client-side and transformed into a Single Page Application experience. You don't need JSON.

The Web API/Front-End Separation using JSON is an anti-pattern.

There will be long-term problems if you deploy Front-End and API separately. If the data structure used to communicate contain domain-specific definitions, it will force the Front-End to write unnecessary code that will break in the future.

In order to not break the Front-End, you'll have to duplicate logic or freeze the API. That will have short-term benefits at the cost of long-term results.

To avoid all those problems, deploy Front-End and API on the same deployable unit. Preferably, using HTML to build the Single Page Application because the browser already knows how to render it.

Don’t use JSON to render a Web page you have control of, use HTML instead.

If you understand and apply these fundamentals, you’ll start to notice the Front-End won’t break that often because it won’t exist anymore. At least not as a separate system.

  • The HTML can be modified if the User Interface has to change.
  • The Web Server internal code can be modified if the logic has to change.

It will increase the cohesion of your web application and nullify Front-End maintenance costs for rendering.

This will require you to give up on this Complexity Bias, backed up by a theoretical “separation of concerns” and unconditional love for {curly braces}.

That is a challenge not everyone might be brave enough to look at.

However, if you have read up to this point, you might as well be the bold one.

See also how to apply these principles for mobile.

Thanks for reading. If you have some feedback, reach out to me on Twitter, Facebook or Github.

Thanks to Mike Amundsen, Jack Skinner and Gary Butler for their insightful inputs to this post.