Recently I had a chance to implement the same functionality with two different client stacks. One of them is using Redux with REST calls and the other React Apollo with GraphQL. I would like to share with you my experience and the benefits and drawbacks of using one or another. Keep in mind that in this blog post I assume that you have at least minimum knowledge what Redux and React Apollo is.
Why I had to do the same feature twice
Because of the way things have evolved in Wix, we have separate developers working on web and mobile. In mobile we are using React Native, so theoretically same person could work on both platforms. But that isn’t the case now. As time went by, web and mobile developers used different stacks for their modules. One of the modules is using Redux with axios REST calls on the web while the mobile part is using React Apollo. Moreover, both web and mobile use their own middleware servers to communicate with backend servers. That means that every time we want to implement something we have to do the same work twice. This is hurting our development velocity. So I’ve decided to see how much work it would take to reduce the amount of duplication that we do. To do that I had to see how both web and mobile are working right now.
This is not a completely fair comparison, since functionality is implemented on different platforms. Also I’ve been using React Apollo for the last year, so I know how to avoid a lot of common pitfalls that come with it. But I’ve tried to keep everything as unbiased as I can.
The feature that I had to develop overall was quite simple. I had to enable editing an order. That includes:
- Creating an Edit Order form.
- Adding a button in the order overview screen which opens the edit order form.
- Adding logic to decide when the button should be displayed.
- Finally sending updated data to the server and updating the order overview with new info.
One of the first issues that I had to solve was implementing logic whether the loader should be displayed when opening edit form or not. User might open the form from other page and have all the data loaded or he might go to the form directly. That meant that with Redux I had to call multiple selectors and check that all relevant form is loaded. That form is loaded through multiple rest calls and saved into multiple reducers, so for this code I had to write unit tests since it was complex.
With React Apollo I didn’t have to think about this at all. React Apollo made sure that if I have the necessary data in the cache it won’t make any additional network calls, but if I don’t, I can just show the loader. All of this is easy to do because data loading is written declaratively. Because this part is so trivial I didn’t have to write any unit tests for it.
Calling the API
Calling the api both with Redux and React Apollo is quite simple. With Redux we emit an action which gets the data out of the store and then calls an api service. With React Apollo we write a mutation query inside the component, and then directly call it inside the component. Once again the amount of code with React Apollo is smaller but personally I don’t like the way you have to write mutations with it. It gets quite messy when you have multiple mutations in one place. React Apollo should get hook support some time in the future, so that should simplify it.
Updating the local state
After updating the order I have to make sure that the data that we display is correct. With Redux that means updating the state inside the reducer. Once again there are some moving parts here, so to feel safe we write unit tests for it.
React Apollo takes care of it completely. Because the returned order has an ID, it will override newly returned data inside its cache. That once again is so trivial that we don’t have to write any unit tests for it (if you want to read more about React Apollo cache you can do it here).
One thing I have to mention is that with Redux, it feels easier to update the state when you create a new item inside the list. With React Apollo you would have to write a query to get the data out of the cache, then update the data and then write new data on top of that query.
Usually the code above scares new people when data becomes more complex.
Whenever we implement a feature we always write an e2e test for it.
In mobile we use Detox while in web we use Puppeteer. The way tests are written are quite different, but what really matters here is how we mock our server calls.
With GraphQL we start a mock server with mocked resolvers. Because GraphQL enforces you to write correct queries, we are quite confident that the things we mock in our tests represent real life scenarios.
If you are using REST the situation becomes more difficult. You have to mock REST calls but there is no good way to enforce that what you mock is going to be true. So even though you may have passing E2E tests you may still get production issues. Another solution is to start the whole server when running E2E tests, but that brings a lot of additional moving parts that can get flaky and writing tests becomes a lot more difficult.
Typescript and IDE support
When you are working on a bigger project Typescript becomes a really important part for it. But writing Typescript means having to write more code. And I really don’t like typing a lot. For me the best coding experience is the one when I can have as much code completion as possible.
Both web and mobile parts of the project are using Typescript. To have proper type checking on Redux we have to write manually the types for actions and payloads, then we have mappings between actions and reducers. When you are typing action names you don’t get any auto completion. When you are creating an interface for that action type once again you don’t get any autocompletion. I think you understand where I am heading.
GraphQL on the other hand has type generation (Apollo Tooling). Also both WebStorm and VS Code have plugins for GraphQL which include autocompletion for your queries. That significantly reduces the amount of manual typing you have to do. And having types allows you to easily jump around the whole project.
The comparison wouldn’t be fair if I didn’t give any benefits for the Redux. In my opinion the main Redux benefit is that we know that it works and we know that it works well. We can assume that we can easily explain to new people how Redux works. It brings a lot of boilerplate but it doesn’t hide any magic inside itself. If you want to have more flavour in Redux there are libraries that can bring it to you. There are libraries that enable less verbose code, add integrations with other libraries and many other things. Redux is just really popular and you know that if you need something you can find it.
React Apollo brings a lot of overhead. It works great while it works. But as soon as you start hitting bumps you are up for a tough time. I can expect new people to get into cases where they can get lost or React Apollo starts to break for what looks like no reason.There are quite a lot of open issues in Github. And usually new people that we hire don’t have any experience with it.
How we are planning to reduce the amount of work
Implementing this feature twice was needed to see how much similarities and differences we have between web and mobile. Moving forward one thing that is really clear is that we need to use one middleware server instead of two. And here GraphQL is a clear winner. GraphQL allows to customise our queries for web and mobile without having to create separate endpoints for it. Also it simplifies our e2e tests because we don’t have to start a real server anymore.
I don’t think we are going to remove Redux from web. That would require us to waste a lot of time without implementing new things.
Comparing statistics of how much code I had to write and how many work hours I had to put in is not fair, because web and mobile infrastructure is too much different. So I am giving these numbers just as a general information.
Working on web took me around 8 work days while working on mobile took around 3. Web took a lot longer because I wasn’t familiar with it while I’ve been working with mobile a lot lately. Also when I worked on mobile I already knew all the edge cases for that functionality. But it is fair to say that writing Redux actions, reducers and selectors with their tests took a lot of time.
Lines of code
Working on web I’ve added around 2500 lines of code, while on mobile it was around 1500. The big part of difference is coming from using React Apollo instead of Redux but definitely not all of 1000 lines.
I hope this post helped you to see what you can expect if you were using Redux with REST or React Apollo. In the end you can definitely live with either one and you should weigh the pros and cons of one or another. If I was to implement a new project I would choose React Apollo, but I wouldn’t rush to rewrite a Redux project to it. But one thing I can recommend is to use GraphQL whenever possible, especially if you think that you might have multiple clients in the future.