Frontend and the Single Responsibility Principle

Juan Manuel Villegas
Mar 26 · 7 min read

One thing I have come across multiple times when jumping into already existing Single Page Application (SPA) projects or when reviewing SPA code in general is the misunderstanding of the role the Frontend has in systems like this. Developers tend to confuse the boundaries of the Frontend’s responsibility by throwing too much stuff at it. This stuff is usually application logic that finds its way into State Management modules (such as Redux-style stores), mixins or components, making the frontend aware of things it shouldn’t, even sometimes duplicating functionality that already exists where it should: in the backend.

Whats the Frontend’s single responsibility?

Note: before we begin, to fully understand the article it is a requirement to know what the Single Responsibility Principle is and why it is important. There is a great blog post by Robert C. Martin on the topic here: Wikipedia doesn’t have a very complete page describing this concept, but they do have one for an equivalent one here:

In this post, we are going to be thinking of the frontend’s responsibility at a very high level of abstraction. Under this view, the responsibility becomes everything related to the rendering of our application: managing routes, displaying forms, executing remote calls to the API, handling the animation of page transitions, opening and closing a side menu, etc. You get the pattern; there is no knowledge about the application domain here. It’s all stuff related to the rendering of the application in a device, be it a browser, a phone or a terminal.

The Frontend’s single responsibility is everything related to rendering an Application in a device, including Route management, animations and display of data.

A good exercise in determining if a certain block of code belongs to the Frontend is to ask yourself the question: if I were to deploy this application on a different device, would I need to copy this code over? If so, then likely whatever the code is doing doesn’t belong to the Frontend and should be abstracted to a reusable library.

Another example of the above idea can be seen when writing the backend of an application. Imagine you are writing a function to handle a route for your API. In this function, there will be a part where you will validate that the request’s verb is the appropriate one(GET, POST, etc), then there will be some parameter validation, and potential data sanitation. After all that, you will start executing your domain code. If, for example, an error happens while validating your data, the function will probably do something like this (Express code):

if (req.query.matchId === null) {
res.send({ code: ‘invalid_data’ });

This block is clearly only relevant in the context of an HTTP request, and thus doesn’t belong to your application domain.

The Problem

Suppose your application is selling some products, and for each product your application charges a fee for the transaction, except for two products for which you know the ID in advance. After the user chooses a product, you display a checkout form and you need to show the user whether they have to pay the fee or not. It’s as simple as:

function hasFee(productId) {
Return [2, 4].indexOf(productId) === -1 ? true : false;

If you drop this function as a getter in your State Management module you can easily access it from anywhere in your application, and quickly solve the problem of displaying the fee to the user.

But, there is a problem with this approach, even within this simplified example. Several problems, actually.

The most obvious one is the exposure of database IDs. If your database IDs change or are different across environments, the function is not going to work anymore. Even if you declared two constants for those IDs in an .env file, you would still have a confusing intersection of domains: database concepts entangled in the frontend of your application. Not to mention that testing by creating dummy objects will be complicated, as IDs are generally not predictable.

The second problem is the worst though- that function is part of the application domain. As such, it doesn’t fit into the high level Single Responsibility we defined above for the Frontend. Let’s dive deeper.

A dumb frontend

In terms of application domain, our frontend must be as dumb as possible. On the other hand, the backend feeding the application must be as smart as possible and concentrate all the knowledge. This type of architecture is called API-centric, and guarantees that at any time it’s possible to reach back to a central authority to resolve any conflicts and fetch relevant data.

An API-centric Application is one where all the domain knowledge is represented in the API, and access to it is exposed through accessors to the different Clients.

This means that our Single Page Application will turn to the API for whatever information it needs. Even for simple stuff like testing if a product has a fee or not.

Following this approach has several advantages, not only at the practical level but also at an architectural level.

  • Avoiding duplication is the primary reason. It’s highly likely that if you needed a function in the frontend to resolve a domain problem, you will also need that function in the backend. This leads, in the best case, to having two functions doing the same thing in two different places. In the worst case though, it leads to two functions trying to do the same thing but in actuality doing a different thing because a developer forgot about the version in the frontend and updated only one when the requirements changed. Even worse is, when you have a situation like the one described in the next bullet.
  • Favoring a multi client design. When the Frontend is restricted to do only what it’s meant to do (display data, allow navigation, communicate errors, etc) it becomes really easy to spawn new clients in new devices. When all the complexity of your domain is properly packed in your API, deploying your application to a new device becomes just a matter of putting up the views, fetching the data, and rendering it.
  • Testing becomes easier. Nothing beats a properly tested codebase- a tenet that is still presently not embraced by a sufficiently large number of developers (at least not at the practical level, meaning that they know and accept the benefits of testing, but still refuse to spend time doing it).

Testing becomes easier basically because separating the domain related code into functions in your backend will ultimately force you to create small libraries, properly exported/imported for usage, rather than creating code that is entangled among other unrelated code. Your function’s signatures will improve because you will have a better understanding of what the function’s real responsibility is after it’s isolated. The benefits are many, and one or more posts will be dedicated to that particular topic. But for now, let’s just emphasize that identifying domain related code and separating it into modules will make your testing easier and better.

  • A dumb frontend is usually easier to scaffold. This is a common pattern: on monday morning your boss asked you to display all the products your store sells in a Slider. So you fired WebStorm, coded a nicely designed Slider component and rendered your Deals. Then, on Tuesday, your boss called you again saying that Sliders are not the hot thing anymore, and instead they’ll be using a ListView to display those products. If your component separated responsibilities, you can easily discard the Slider component, import all the domain code and write the ListView component.

When all the complexity of your domain is properly packed in your API, deploying your application to a new device becomes just a matter of putting up the views, fetching the data and rendering it.


There is another reason to make your Frontend dumb which isn’t strictly related to an architectural concept but rather to the fact that the device in charge of displaying your data is usually going to be less powerful, sometimes much less powerful, than the machine, or potentially the grid of machines serving your backend code. For this reason alone, you don’t want code like this in your views:

<div class=”cart”>
<div class=”cart__total”>
{{ (price + vatTax) * percentDiscount + someExtraCharge }

I know. It’s just a simply expression, any device can easily solve it right? It turns out that SPA code in general isn’t quite performant (although we’ve seen great improvements in the recent years), specially when your audience isn’t strictly constrained to modern devices. Plus, it already has a bunch of other stuff to attend: routing, caching, solving those meta tags for your SEO team, not to mention rendering HTML and maintaining the shadow DOM, and the list goes on. So, the more stuff you can take out from it, the better!

Final Words

In this post we’ve gone through an overview of what the Single Responsibility Principle is and how it impacts the frontend code of our application. We’ve seen code that works and fulfill functional requirements, but that violates architectural principles and isn’t scalable in the long term. We have also listed the benefits of having a properly organized domain-related code.

As a summary, the most important things to do are then to be able to identify what belongs to the frontend and what does not, and be able to factorize it out into library code, which will probably end up residing in your backend code. The benefits for this will be a single source of truth for the stuff that really matters in your project, which is the Domain Knowledge, and improvements in terms of scalability, readability and testing capacity.

Thanks to Paula Cepeda Gallo

Juan Manuel Villegas

Written by

Software Engineer. Currently working at Smylen.

The Startup

Medium's largest active publication, followed by +606K people. Follow to join our community.

More From Medium

More from The Startup

More from The Startup

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade