Jonathan Levi • November 23rd, 2022
Shipping quickly, safely and asynchronously with GraphQL
This is the first article in a series that explores and explains the technology behind Studius. We'd love to know whether you find this useful or not! Don't hesitate to reach out with feedback: team[at]studius.ai
Hirsh and I have worked on systems that use a bunch of different transport layers in production: REST, RPC and GraphQL. When the time came around to pick ours for Studius, we considered all of them and eventually decided on GraphQL. After a year and a half of use in production, we’ve learned lessons that we think are worth sharing. In this article, we’ll explore our initial reasoning, setup and experience over time.
Before we get into the details and draw up the (dis)advantages we’ve encountered, there are a few non-technological constraints that have contributed to this choice. Hirsh is at Medical School spending shifts in hospitals and I’m a full-time Software Engineer at a startup. By circumstance, we’ve worked separate hours on Studius from the first line of code we wrote. So we needed a setup that enabled us to be productive and minimized blocking each other.
Although I tend to be a proponent of monorepos (and monoliths to start side-projects like this one!), we felt it was wisest to split the client and server into separate Git repos. That way we could each setup our environments as we liked and wouldn’t need to spend time on build tools or managing multiple environments in the same repo. It also helped that Hirsh prefers backend work and I prefer the frontend side. The single connector between server and client is a GraphQL schema: it provides a typesafe contract that both client and server need to adhere to, to play well together. Let’s now dive into the advantages – do keep in mind I’m the person writing client-side code 99% of the time, so there is a clear bias towards the client!
Advantages of using GraphQL
For context, I’ve been learning and writing React code for the past 6 years and Create React App was my preferred way to start up a project quickly. Though we have since migrated to Vite, which is a phenomenal piece of software, the app is built statically. We serve the app through Firebase CDN hosting, which is almost free, fast and creates preview deployments on a Github PR. This entire approach is fully compatible with GraphQL.
Also bear in mind that the following points are all made given our specific integration of GraphQL – there may be more/fewer (dis)advantages if we had made different implementation decisions.
Asynchronous collaboration: First, Hirsh writes server-side code and updates the GraphQL schema. Afterwards, I can open the schema on Github and use the queries and mutations with fully typed inputs and outputs. Except for the (very) rare server errors, there are no surprises.
Language/framework agnostic: our server runs on Java, the client side is a React app written in TypeScript. If we wanted to build an Android or iOS app, we could start without modifying any server-side interactions. Nowadays most environments have excellent support for GraphQL.
Types: we have strong conviction in building with strongly typed languages. We got burnt spending countless hours at different companies dealing with broken, legacy code in dynamic languages. Strongly typed languages help us park the project during busy weeks and get back to it seamlessly because we have much higher trust in code that we wrote in the past. A GraphQL schema guarantees a typed transport layer for client and server.
Apollo: on a lucky day you find a library that makes you feel more productive and Apollo is definitely one of them. I added a custom package.json script
npm run gen that fetches the latest schema from the server, maps the GraphQL to TypeScript types and allows writing fully typesafe interactions with the server. With Links, we debounced queries and integrated Sentry for error reporting in a breeze. The global state and caching layer also help fetch data efficiently and share it across components, without a React context or Redux.
Disadvantages of using GraphQL
Sequential server/client shipping: though inherent to every architecture where the server and client aren’t shipped as a monolith, we’ve particularly felt this during our development as we don’t work on Studius every single day and don’t work on it during the same hours. Sometimes days may go by between the server implementation, followed by the client one. It’s not really a disadvantage of GraphQL per se, but still worth pointing out.
GraphQL problems: there are some major drawbacks with the GraphQL approach, inherent to the protocol, such as the N+1 problem and caching. We haven’t hit the scale where we feel we are paying this cost yet.
Apollo: for all its awesome advantages, it’s a large package (1.2MB minified)
Scaling to more engineers: another point that’s not directly about GraphQL, but we wonder how this architecture would scale to a team of a dozen engineers and beyond. While it works well for our small team of 2, productivity may actually decrease for a larger team, mostly due to coordinating changes between multiple people and two repos. We may also be at a disadvantage silo’ing people into a “backend” world and a separate “frontend” world, where it’s hard to move across the stack seamlessly due to a different language, framework and tools. Note: we have no plans to increase our team in the near future, but since starting any software project is a series of tradeoffs between immediate speed and future maintainability, it’s worth a mention.
Would we use it again?
Yes, absolutely. GraphQL is fantastic for teams who want to decouple services and make them interact with type safety. That said, I would consider taking another look at gRPC and newer projects like tRPC for what I build next.