How we used GraphQL in development on the example of the Internet catalog

5 min


In this article, we share our experience of real-world use of GraphQL to create an online catalog. Let’s talk about the advantages and disadvantages of this query language we found when using the GraphQL + Apollo Client, Next JS (SSR) + TypeScript stack.

Disclaimer: we do not describe in detail the “magic essence” and the history of the instrument’s origin (a lot has already been said about this). We hope that those who are already familiar with GraphQL will benefit from our practical experience.

We recently took part in the creation of an Internet catalog of lighting equipment. To create advertising pages, site administrators could use the designer: select the necessary blocks (for example, banners or lists), fill them with data, define the display order and other settings. At the same time, the application rendered components that were pre-laid out for each block type.

In each category of the online catalog, cards of various product groups were displayed, and when you hover over the card, a list of properties for the attached products was displayed.

We needed to display the properties of products in a tree structure and provide a sufficiently high speed of processing requests.

We have chosen the following order of work with requests:

  1. Request all product groups for a specific category (usually about 50 groups).
  2. Request a list of products for each group.
  3. Request a list of properties for each product.

Since we were developing an application based on GraphQL, we were prepared for the fact that some of the data would have a rather complex nested structure. Although branching this structure was logical for backend development, it was necessary to write some “extra” logic on the front in order to process the data and output it to the component, according to the design.

Due to the nature of the GraphQL constructor, we decided to collect properties and unique values ​​not on the back, but on the front, and then render them in a specific order. However, the processing of the request was too slow – up to 20 seconds, which, of course, did not suit us.

For this reason, we began to divide each request into small subqueries and load data in portions. As a result, the application noticeably improved in speed – requests took no more than 2 seconds. Although the number of requests has increased, the load on the system has decreased, the need to load unused data has disappeared.

Next, we will tell you more about working with GraphQL in more detail.

Features of working with GraphQL

According to the product requirements, we should have used the query language GraphQLdeveloped by Facebook. For this reason, we did not indulge in endless arguments about which is better, GraphQL or REST – instead, we decided to use the right technology in the most efficient way, taking into account all its strengths.

We took into account that GraphQL was designed to simplify the development and maintenance of APIs, primarily by having a single endpoint.

GET	/news
GET	/posts
POST	/news
POST	/post

GraphQL has a single endpoint. This means that we don’t need to make two separate requests to get data from two different resources. GraphQL consolidates all requests and mutations into one endpoint and makes it available for reference, as well as avoids the versioning inherent in REST APIs.

GraphQL provides the ability to optimize performance and get exactly the data that is needed at the moment using a special query syntax: the required fields must be listed in the query.

const FETCH_USER_DATA = gql`
 query FetchUserData {
   user {
     firstName
     lastName
     date
   }
 }
`;

GraphQL uses strongly typed entities and type schema, which in turn comes in handy in conjunction with TypeScript and front-end type generation.

Many of these features can certainly be implemented on REST APIs, however, GraphQL provides them out of the box.

For client interaction with GraphQL, we chose the most popular solution with good documentation – the Apollo Client library, which allows you to get, cache and modify application data. Apollo Client gives you the ability to use request and mutation hooks and tools to easily track download / error status.

Also on the front, we used the NextJS framework, chosen taking into account the following factors: pre-rendering (NextJS provides a very simple mechanism for implementing static generation and SSR out of the box), support for all existing css-in-js solutions, dynamic routing, support for static files (e.g. images) in React components.

Finally, once the technologies are selected, let’s move on to development. At first glance, everything looks good: modern libraries, good documentation, many different use cases. Each of the technologies individually is designed to facilitate comfortable and fast development. By creating a boilerplate, which was indispensable, and designing UI components, we gradually approached the stage of effective interaction between our libraries. Here all the fun began.

Looking deeper into NextJS mechanisms, we can see that it uses two forms of pre-renderer: static generation and SSR. Both of these strategies are implemented using special data preloading functions:

`getInitialProps` (SSR) – on first load, it runs on the server, requests data and then passes it to the component as props.

function Component ({data}) {
 ...
}
 
Component.getInitialProps = async (ctx) => {
 const res = await fetch('https://...')
 const json = await res.json()
 return { data: json.data }
}

`getStaticProps` (Static Generation) – runs at the build stage. NextJS pre-renders the page using the props returned from this function.

export async function getStaticProps(context) {
 return {
   props: {}, // передает данные в компонент в качестве props
 }
}

`getServerSideProps` (Server Side Rendering) – NextJS pre-renders the page on every request, using the data returned from this function as props.

export async function getServerSideProps(context) {
 return {
   props: {}, // передает данные в компонент в качестве props
 }
}

Thus, all the listed functions are declared outside the component, receive some data and pass it to the component. This leads to one of the problems of interaction with the Apollo Client. The library provides such query mechanisms as the `useQuery` hook and the` Query` component, and none of the proposed methods can be used outside the component. With this in mind, in our project, we decided to use the next-with-apollo library and in the end we were satisfied with the result.

Another known problem in Apollo Client, which we also encountered, is the possibility of an infinite loop of requests from the `useQuery` hook. The crux of the problem is that the Apollo Client continues to send requests indefinitely until it successfully receives data. This can lead to a situation where the component “hangs” in an endless request, if the server cannot return data for some reason.

In our case, one of the reasons was the change in the scheme on the back. Apollo generates the types on its own, so when writing queries on the front, we had to follow the generated types to avoid bugs. For example, we had a working request, without any type issues. However, when changing the schema on the backplane, the types changed at the same time, because of which the working request could stop functioning. Given this, it is optimal to immediately implement logging and a clear error handling system in order to save the team’s nerves and time.

In our opinion, it turned out quite useful that in graphql queries you can specify exactly what data should be received. When sending a large number of requests, this avoids processing unnecessary data. In turn, this can significantly affect performance as the amount of data grows.

It is worth noting that one of the advantages of GraphQL is considered the convenience of development and API support, but this property is more significant for backend development and, according to our observations, does not have a significant impact on the front. Due to the generation of types, every time the schema was changed on the backplane, we rewrote all affected queries. Beck also had to refine the scheme if we needed to get something on the front that had not yet been implemented. At the same time, the generation of types when using TypeScript made it possible to catch many errors at the stage of writing the code.

Summing up

According to our observations, GraphQL is widely used in various types of IT solutions and provides certain benefits for the development team. Let’s summarize the main features of GraphQL that we encountered while developing the project:

  • Generation of types. Generating graphql types saves time when writing queries, is ideal for using TypeScript, and helps you catch nasty bugs early in development.
  • The speed of data updating. With GraphQL, in our case, it was possible to quickly update data in the front-end of the application. The developers had the opportunity to make changes on the client side without interfering with the server (if the necessary fields were available on the backend, of course). At the same time, graphql has a so-called “sandbox” for queries, in which you can test queries with different parameters
  • Processing only the data you need. In graphql queries, you can specify exactly what data should be received. When sending a large number of requests, this avoids processing unnecessary data.
  • Scalability. GraphQL makes it easier to work with scalable systems by making it easier to combine data from multiple services into one.

Thanks for your attention! We hope this example was helpful to you.


0 Comments

Leave a Reply