SSTool – Headless Drupal with React

Client
  • Omitsis
Technologies
Services
Date
  • 12/05/2022

Adevinta Spain asked us, from the Drupal department of Omitsis, to improve their team self-assessment tool (Self Sufficient Teams or SST). Adevinta is the super company that manages the marketplaces Fotocasa, habitaclia, InfoJobs, coches.net, motos.net and Milanuncios, each with millions of visits. They are mega experts, very professional and also very nice.

The tool was in WordPress and had served them very well, but they needed to apply some improvements. When evaluating them, we both saw that the best solution was to create a backend with Drupal 9 and a frontend with React. That is, a Headless Drupal with React.

One of the advantages is that they, being React experts, could get involved in the frontend development at any time. Obviously, another great advantage of using React was that the frontend could be more dynamic, with instant changes to user actions.

And Drupal, as a backend, has been more than proven to be robust and at the same time flexible to the project’s needs.

Additionally, being free software, it’s not necessary to pay monthly fees for usage licenses like other proprietary CMS (Adobe Experience Cloud, Liferay, Contentful, etc)

The tool

As a quick summary of how the tool works, we can say that it’s a series of self-assessment tests. Each test consists of a series of grouped questions.

Users can answer yes or no to a question, and add a comment. Depending on their answers, they are given a score per question group and a final score.

To improve as a team, actions can be created, things that the team thinks will help them go from no to yes, and they commit to doing.

The backend: Headless Drupal 9

As we’ve already mentioned, for the backend we used the latest version of Drupal 9 with the following special features:

Custom entities

Instead of using node bundles (content types), we saw that it made more sense to create custom entities for each of the structures required for our application. This needed very specific specifications, such as the possibility of creating new test templates without modifying past tests.

Migration from WordPress to Drupal 9

As always at Omitsis we approach a migration using the fantastic migrate module. For that, a source plugin was created for each entity to migrate, plus several process plugins, to be able to transform the WordPress data into the form that Drupal needs.

JSON:API

To pass information between the frontend and the backend we used JSON:API, already in Drupal core for some time.

In JSON:API, referenced fields are sent in a different array from the returned array of the requested entity’s data. Let’s say I make a request for an entity called recipe and it has ingredient fields.

When we tell JSON:API to include the ingredient information, it doesn’t do it within each recipe, because if we have a thousand recipes and the vast majority contain the ingredient onion, it would include the complete onion information many times.

Instead of that, a reference (an id) to the onion entity is included, but the onion’s fields are in an includes array and, therefore, will only be once, and will take up much less space.

But this is very impractical when obtained in React, because for each entity you have to search for all its nested entities, and more if there is more than one level.

For that, the JSON:API Include module exists but, after evaluating it, we dismissed it because it made our requests, with the nested architecture we had, too heavy.

Instead of that, we saw it was much more efficient to do the same process on the client with the jsona package. This way, we had lighter requests and in React a much more practical data structure.

Additionally, to improve request performance, whenever possible we combined all requests into one, saving the network layer cost of each request. For that, there is the excellent subrequests module, very well explained here by Mateu, its author.We also used Open API, so the frontend team could have a very clear and easy way to know all the JSON:API endpoints, filters, etc.

Backend administration

To facilitate administration in the backend, the Gin theme was used, along with the Gin toolbar. A simple dashboard with the most important shortcuts was also created.

Additionally, special care was taken with the entity listings and forms, to make them as usable as possible. And with the help of the Corresponding Entity References module, creating cross-references was facilitated, just by editing from one side.

Adevinta asked us that, to access the backend and frontend, it could be done through Okta. To manage SSO (single sign on) with Okta in the backend we used the SAML SP 2.0 Single Sign On (SSO) – SAML Service Provider module

Data table exports

To integrate the results with their data lake, we created some CSV exports, which were generated with cron (with the help of ultimate cron) every night. These exports were uploaded directly to an Adevinta S3 bucket, using the S3 File System module.

Frontend with React

We created the React application using create-react-app. Being a private frontend, it wasn’t necessary to use tools to create static code or that would run on the server (Server Side Rendering)

The design of the public part was assumed by our fantastic design department at Omitsis. They met with the client to understand their needs and thus be able to create the first wireframes.

Wireframe of the main page, showing the team list.

Team page, with their evaluations and actions.

Evaluation wireframe, with question groups and their results.

Evaluation wireframe, zoomed in on a question part.

Once created and validated, the design of each page was created, using Adevinta’s design system:

Adevinta design system

Team page design

Team page design

Evaluation page design

Evaluation page design, zoom on a question

React frontend performance improvements

To ensure everything worked as smoothly as possible, the following aspects were considered:

Minimize requests

Better two requests than three, and one even better than two. Thanks to JSON:API you can include all the information you need in a single request: the entity, the related entities and the related entities of the related entities. Like this, up to the level we want.

Include only what’s necessary in JSON:API

Related to the previous matter, if we add many related entities with all their fields, we can find ourselves with a very complex internal query. This can make it need a lot of memory and the resulting JSON file too heavy.

To avoid this, everything that is not needed must be pruned, using Sparse Fieldsets that allows specifying which fields we want returned from each entity.

Group requests

As we mentioned before, we used the subrequests module extensively to group more than one request, when possible. This way we save the network layer time.

Don’t use JSON:API Include

Also as we mentioned before, in our case using JSON:API Include wasn’t a good idea, since we could have requests that returned JSON that was too large (> 1MB).

We installed the jsona package and just by doing this:

import Jsona from 'jsona';
const dataFormatter = new Jsona();

It converts the JSON we get from JSON:API into one more friendly for the programmer.

const json = {
    data: {
          type: 'town',
          id: '123',
          attributes: {
              name: 'Barcelona'
          },
          relationships: {
              country: {
                  data: {
                      type: 'country',
                      id: '32'
                  }
              }
          }
    },
    included: [{
        type: 'country',
        id: '32',
        attributes: {
            name: 'Spain'
        }
    }]
};

const town = dataFormatter.deserialize(json);
console.log(town); // will output:
/* {
    type: 'town',
    id: '123',
    name: 'Barcelona',
    country: {
        type: 'country',
        id: '32',
        name: 'Spain'
    },
    relationshipNames: ['country']
} */

Skeleton

Instead of showing loaders, we thought it was better to use a skeleton while data is loading. This is almost an industry standard now, since the perceived performance by the user is higher.

Single Sign On with Okta

To access the frontend, users needed to validate themselves with Okta. To achieve this, we used the Okta React package and Okta Auth js.

Users when entering the site, if they weren’t authenticated, were redirected to Adevinta’s Okta login.

Continuous integration with Amazon

Using Elastic Beanstalk, CodePipeline, EC2 and CloudWatch from Amazon, our systems department created a continuous integration process.

With CodePipeline it “listens” to changes in the repository branches: master for the production environment, staging for the staging environment. When it sees there’s a change, it makes a new deploy using Elastic Beanstalk.

Then Elastic Beanstalk takes care of creating a new EC2 instance, does the necessary tests and if everything goes well, swaps it for the previous one. If it doesn’t pass the tests, it doesn’t replace it and deletes it.

Conclusion

We have a lot of experience creating high-quality projects, user-centered, with Drupal and React, but we particularly liked this project.

For being a great example of Headless Drupal with React and because working with Adevinta Spain is a pleasure: they are accessible, knew how to create excellent specifications, fast, have resources and are very good people.

JU

julia

manager

Recent Posts