Front-end development has evolved in the last few years, and has become a complex beast on its own. This series of blog posts will take us through the process of building a React walking skeleton from scratch for client-side applications.
We will outline, and maybe implement, a small React application that will serve us as a playground. The scope will be small enough to set up all the pieces of the system without getting lost into the details of developing complex features. One important aspect will be CI/CD, a common development practice these days, even for front-end applications.
What is a walking skeleton?
A walking skeleton is the minimum working code path that sets the foundations of what will be a complete application. This typically includes a very thin vertical slice of a minimal feature that is tested and deployed into a production environment, connecting all the major components involved, such as external APIs, end-to-end.
Building a walking skeleton, as a first step when starting building a new project, is important because it establishes the necessary foundations for developing features. This helps minimising the problems and bottlenecks down the road, especially when deploying into production. The idea behind a walking skeleton is similar to assembling the production line before starting the mass production of the cars in a factory, making sure everything is ready before the bulk of the work begins.
Defining our React application
Now that we know what a walking skeleton is about, we need to define what it needs to include for our use case. This will be driven by the kind of application to be built. We will go for a typical list/detail client-side application that communicates with an API to fetch data and display it to the user.
For the purpose of simplicity, and to focus on the client side, we’ll use a free API. There are many free APIs available out there. One of them is Kitsu, which is an anime API to be up to date with the latest trends, keep track of your series, and socialise with like-minded folks. I choose this API because it’s free, it seems quite stable, and I’m an anime fan 🙂 so it fits the purpose perfectly. In case of aversion towards this topic, any other similar API would be suitable for following along this series.
While Kitsu API offers a wide range of endpoints to get different kinds of data, from series lists to adding comments and reactions, we will be using a very small subset of it. For the purpose of our walking skeleton, we just need one feature that exercises the system end-to-end. This feature should be as simple as possible, so we don’t get distracted from the main goal. One suitable feature is to display a list of the trending anime series.
Let’s create our first user story:
As a user,
I want to see a list of trending anime series,
So that I can stay up-to-date.
This small user story is enough to force us to set up all the pieces that we need for our application.
Defining the walking skeleton requirements
We need to set up the basics of the infrastructure for our walking skeleton. Before that, we should decide on the architecture of our application.
In order to be able to develop a list-detail application, we would need the following tools:
- API calls to get the data
- Support for styling
- Automated tests
- Basic development workflow and setup
- Deploying to live
Please note that the choices made below can differ between projects and personal preferences. React is the only library that is the bread and butter of our exercise. Using different tools that suit you or your project better should be completely fine and mostly compatible with the walking skeleton that we are building in this series.
There are a few libraries that are widely adopted. axios is one of them, and it’s a bit simpler compared to alternatives such as `fetch`, so we’ll go with it.
For styling web front-end’s, there are so many options in 2022 that it’s even overwhelming. For the sake of reaching a compromise between maturity and simplicity, and given that we won’t need theming, we will go for sass.
We need to define our testing strategy. There are different approaches, and with them, different libraries available. One approach that has suited me well recently and seems to have community adoption is React Testing Library + Jest + Cypress. React Testing Library and Jest can be used for React and unit tests, while Cypress is useful for end-to-end testing. More specifically:
- Our React tests will mock the API and test from as close as the user’s perspective as we can. These tests could be called _integrated tests_, as they will be testing several React components, most of which should be simple enough not to need a focused unit test. This should be the bulk of the test suite.
- Some building blocks will have enough logic and variations to benefit from actual unit tests, such as data transformations or formatting.
- Cypress will provide the remaining confidence by testing the system as a whole, that is, the application with the external API.
NOTE: While this testing strategy is simple enough to cover a good amount of complexity, more complex projects will require a different approach depending on the specific needs. For example, projects with many API dependencies can benefit from Consumer Driven Contract testing.
For mocking API calls, we could use msw, which intercepts requests at the network level, making it close to an actual external API, without the overhead of an HTTP server, and can be used on the browser to stub data.
We developers often appreciate having tools and setups that make our lives easier when working on a project. While we won’t aim for _the ultimate collection of tools we might not even use_, we still want the bare minimum to make working on a project bearable. Every project benefits from different kinds of setup, and every developer has slightly different preferences too. Even for our small project, it would be nice to have:
A local development environment that is as close to production as possible
Given this is a client-side application, we will need a separate development build on top of the production build. But we should be able to build and run the production build locally as well. In general, we want concise scripts or commands that make running things a pleasant experience without having to deal with the details on a regular basis.
Code linting and formatting
It is important to catch programmatic and stylistic errors before building our application. There are two main choices: standard or eslint + prettier.
While having a predefined set of rules is great to get started, we want to be able to customise the linter to suit our coding style. So we will go for eslint + prettier, even if the setup is less straightforward.
In order to avoid restarting the local development server to check every change, we can set up a hot reloading mechanism.
There are a couple of tools available for hot reloading. While React Hot Loader has been the way to go for a long time, React Fast Refresh is now the recommended approach moving forward. Fast Refresh comes with an npm package and a (non-official) Webpack plugin.
In order to have a smooth CI/CD workflow, we are going to do Continuous Deployment. While this is a bold practice, I have seen it working successfully, reducing bugs, QA time, and overall stress, as well as increasing the frequency of value delivered to the customers. However, it needs to be implemented right in order to avoid a chaotic development workflow. This is why designing the walking skeleton with Continuous Deployment in mind from the beginning is so important.
The number of deployed environments varies depending on the project, from a dozen or more integration environments plus staging and production, to just production. Since our application is a client-side application with a single, external dependency, it wouldn’t benefit much from multiple environments. Therefore, we will set up just a single environment: production.
Since every merge to master will trigger the release into production pipeline, the focus on automated testing is of paramount importance. Also, the local development setup should be as close to the production one as possible. This minimises any differences and gives us confidence that if local testing is successful and all the automated tests pass, it is safe to deploy to live.
We will make sure that building the production bundle is repeatable on both pipelines and any development machine, using Docker containers and build scripts. In general, we will use Docker as much as possible to make any kind of builds or test runs repeatable. Some automated tests will run against the production build, as well as smoke tests after the bundle has been deployed, to make sure that we run some sanity checks in an automated way (this step is optional, but it is a nice-to-have).
In an ideal world, the people performing the manual checks before integrating the new code, would be able to pull a development branch, check that the work done satisfies the requirements, and give their thumbs up. Automated tests will be run both on development branches and the master branch before deployments, to make sure that the system behaves as expected and doesn’t have any obvious regressions. Optionally, we could add other checks such as code quality, code coverage, or Lighthouse, to help keep the overall quality under control.
The pipelines will be implemented on GitHub Actions, which gives us enough flexibility without a complex setup, and is free for public repositories. There are many other alternatives such as GitLab, Bitbucket, TravisCI, Jenkins, and more. Since we will rely on Docker as much as possible to execute most of our automation, swapping the automation server should be possible without major changes.
To host our application we will use GitHub Pages. Once more, there is a wide range of options, such as Heroku, Netlify, or AWS services, among others. For simplicity, and given that we will make use of GitHub anyway, we will go for GitHub Pages. Please, feel free to use your hosting platform of choice instead if that suits your needs better.
We have defined what our walking skeleton is going to consist of. In the next post, we will start implementing our walking skeleton, which will be deployed into production.