The front-end engineering team at 1stdibs used Backbone extensively starting in 2012 through 2015. When React began to rise in popularity we saw the benefits of React Components over Backbone views. Pretty soon we were using React exclusively for new code, and rewriting old Backbone views in React as needed. A year later, many parts of the site use React exclusively for the view layer.
But what about the remaining Backbone models and collections? Over time we accumulated a large library containing over 200 distinct Backbone models and collections. Each of them represent a different type of data that is modeled by our back-end services. All of the logic for interfacing with our back-end services and much of our business logic were in these models and collections. Several 1stdibs applications consumed this library. The code in our Backbone models and collections typically fell into three categories:
- Methods to get data from the services (url, fetch + custom methods that called these)
- Methods to transform or access data in the models, e.g.:
hasBeenShipped() { return _.contains(this.get(‘shipmentStatus’), [‘SHIPPED’, ‘DELIVERED’]); }
- Methods to save data to the services (save, destroy + custom methods that called these)
We ran into several issues with this library:
- Dependency size – models often have dependencies on other models. As we added more and more of them, these dependencies became more interwoven. Adding one model to your application could bloat your dependency graph to encompass practically every model. This could result in adding 500k of minified JavaScript!
- Fragility – We were constantly iterating on it. Ensuring there were no breaking changes was difficult.
- Maintainability – There wasn’t a clean interface between this library and the applications consuming it.
- Complexity – It was hard to understand many models in an application with an MVC architecture. A change in one model could trigger changes in others that cascaded through the system. The stateful and object-oriented nature of models did not fit nicely with React’s functional paradigms.
While it was relatively easy to migrate Backbone views to React Components, solving the above problems were more difficult. We wrote some code with React and the flux pattern, keeping models in stores. But enforcing one-way data flow is hard when any consumer could also edit the state of the model. We also considered breaking the interwoven model dependencies, but never came up with a great solution.
We started researching GraphQL and Relay and realized that it approaches our problem in a very different way. We could implement these models as data types in a GraphQL server! This would give us a convenient way to access any data we need. For the three categories of model code:
- Data retrieval methods – the GraphQL server implements data-access and can present any nested structure the client requires
- Data transformation and custom accessors – implemented as custom fields. The code that lived in the model’s method can easily be moved to a GraphQL field’s
resolve
function. - Data persistence methods – implemented as GraphQL mutations.
Essentially, we moved the code that was in our models to our GraphQL server. We starting using GraphQL and Relay in a few applications and we’ve seen a lot of benefits:
- Send less data to the client: Since we specify exactly the fields we need we are sending much less data to the client. For a buyer looking at a full page of orders, the data response went from over 1MB to about 90KB.
- Send less code to the client: Prior to using GraphQL, we would send all of the data from the services to the client in addition to our client-side representations of it. With custom fields the benefits are twofold: less data and less code is sent to the client.
- Relay is very performance focused and has a “pit of success.” It’s very difficult to over fetch data and smart caching is the default.
- GraphQL’s built-in introspection gives us type-ahead and documentation right in the IDE.
- Our backend engineers also love GraphQL! They can now make their services more RESTful and leave the deep inflations to the GraphQL server. They also benefit from the GraphQL server acting as a compatibility layer. A lot of breaking changes can be smoothed over in the GraphQL server.
We have now been using GraphQL and Relay on some of our apps for a few months, and have been very happy with the results. These tools have provided us with a clear way to directly convert a lot of our Backbone code over to React. This allows us to create code that runs faster, is easier to read and maintain, and allows us to interface better with our backend developers. GraphQL and Relay are also relatively easy to pick up, so more of our front-end team can start using it and seeing the benefits quickly. We expect to see more of our code using GraphQL and Relay soon.