Pothibo

Losing context with JSON

There's nothing that divide people more than JavaScript framework opinion. People have all sort of reason to choose one over the other.

But one thing people seem to agree on, is that your project should consist of a MVC framework for the front-end and consume an API provided by your backend.

I disagree.

When you choose to consume an API for your own backend, you make a few implicit decisions about your system.

You implicitly choose to lose the context of every request you make to the backend.

That sounds vague but it's not. It's common sense. When a user click on a link on your application, the server receives an URL and cookies. Together, the server builds a profile for that request (route to a controller, user from a session's cookie, GET/POST param, etc.). Everything sent is analyzed to give context to that request.

The server knows you want more detail about product #123 and knows you have logged in. It builds a context around the request.

When all is ready to be sent back to the user, the server sends a response in form of HTML that contains a lot more information than what the user asked.

It returns the right HTML semantic for the content requested. The browser can render the response as is and the user should have exactly what he asked for.

When you deal with an API, you go back to a form of stateless HTTP that you need to process to understand what exactly you asked from the server.

This is what I meant by losing context.

To show the difference, I am going to pretend that I am building an eBay clone.

For this clone, I'll build a page that shows a list of products. Here's the basics features I this page to have:

  • List all products;
  • If the user is logged in, show a buy button;
  • Paginate the results.

Version #1: HTML

views/products/index.html.erb
<table class="products"> <thead> <tr> <th>Product name:</th> <th>Price:</th> <th>Action:</th> </tr> </thead> <tbody> <% products.each do |product| %> <tr> <td><%= product.name %></td> <td><%= product.price %> $</td> <% if signed_in? %> <td><%= buy_product_button(product) %> $</td> <% end %> </tr> <% end %> </tbody> <tfoot> <div class='pagination'> <% unless products.page != 1 %> <%= link_to previous_page(products), class: %w(previous) %> <% end %> <span><%= products.page_start %> of <%= products.length %></span> <% unless products.page != products.max_page %> <%= link_to next_page(products), class: %w(next) %> <% end %> </div> </tfoot> </table>

When this page is sent back to the browser, the browser will render it as-is. The context of the request is carried over with the response.

No post-processing, no front-end algorithm. The pagination and links associated with it are already rendered properly.

Everything is ready for the user to keep shopping.

Version #2: JSON API

views/products/index.json.jbuilder
json.products products do |product| json.name product.name json.price product.price if signed_in? json.buy_url buy_product_url(product) end end if products.page != 1 json.prev_page = previous_page(products) json.next_page if products.page != products.max_page json.next_page = next_page(products) end json.start_page products.page_start json.total_count products.length

Here's an example of what this view could be rendered into.

{ "products": [{ "name": "test", "price": 1.00, "buy_url": "/some_url/products/1" }], "next_page": "/some_url/products?page=2", "start_page": 0, "total_count": 532 }

When you receive this response from the server, it's impossible to generate a view without querying the data first.

To render the HTML, you will have to analyze the data and generate a template that suits the data you received.

  • Is the user logged in?
  • Should I show a previous link on the pagination?
  • Should I show a next link on the pagination?

All those questions that you already addressed in the backend have to be answered once again.

Then, things can change, things will change. And whatever condition you change on the backend will have to be adapted on the front-end.

Let's say you remove the buy button for each product when the user is not logged in. Now, all the checking if the user is logged in when rendering the server side template needs to be removed. Want it back? You get my drift.

And this is a very basic example that happens everyday. When things get even more complicated, some JS framework provide routing so that these framework can retrieve the context they have lost through the use of an API.

What happen if you decide to change your routing for some reason? This will lead to a watershed.

Building hybrid response

I believe that full page HTML response and API are the two opposite extremes when it comes to handling response from the server.

Sometimes, you want dynamic pages. They have their usefulness.

Instead of relying only on JSON data, you can use an hybrid method that will limit the amount of context you lose.

The goal is to keep render as much HTML as you can on the server. Remember that JSON is a JavaScript object. And you can store anything you want into a JavaScript object. It's possible to render critical HTML fragment on the server side and pass it to off to the front-end through a JavaScript object.

This is how I do it. If you're a rails developer, it's easy and I've already discussed how to use custom event to update the DOM. If this post rung a bell for you, read it. You'll enjoy it.

This alternative makes use of browser events as a routing system. Server-rendered HTML can be used to swap the old DOM with the new one received.

Dumbed down front-end; same dynamic property.

And when you edit your HTML? You change it on the server, the front-end will just use the updated version. No need to change a single line of your front-end.

The hybrid method I discuss here might be new to you. You might have a urge to fight it off. Don't. Let it sink for a few minutes.

I started using this technique 6 months ago. I'm not looking back at MVC frameworks.

Get more ideas like this to your inbox

You will never receive spam, ever.