GraphQL has come a long way since Facebook first announced this open-sourced spec in 2015. Since that debut, prominent companies have embraced this technology to enhance feature velocity and shrink product development cycles. To mention some, Fortune 500 enterprises, such as Intuit, Walmart, The New York Times, and NBC have employed GraphQL. Also, refusing to be left behind, global startups, such as Airbnb, Docker, Github, Uber and Yelp are just a few of those who have followed suite and added this technology to its long-term strategy.
To smooth out your implementation of the GraphQL backend at your company, here are five things you need to keep in mind:
- The learning curve when it comes to Implementing a GraphQL server from scratch is hard. But the ever-improving availability of community tooling is making the work easier.
- It is very important to figure out where a GraphQL server fits into your current back-end architecture.
- You need to carefully think through your GraphQL server's performance. Again, community tooling can help.
- Securing GraphQL APIs and queries is inherently different compared to REST APIs, especially with data access control lists (ACL).
- Support for specialized data, such as real-time data or data types such as geo-location, needs to be explicitly added.
GraphQL knowledge curve
To implement a GraphQL server you need to:
- Author a GraphQL schema that defines:
- The data types that your API will interact with and the relationships between them.
- Specification for queries (for reads) and mutations (for writes).
- Write resolvers for queries and mutations - the code that actually executes the queries (i.e. talks to and/or manipulates your sources of data such as databases, REST, GraphQL APIs, etc.). Resolvers need to handle errors and exceptions from the data source and, just as a with a normal GraphQL response, convert the corresponding responses to JSON. If you want to support source specific features like custom SQL functions, stored procedures, etc., this needs to be factored in as well.
- If you need event-driven real-time APIs (similar to the push equivalents in REST like Webhooks and Websockets: see How and Why To Provide Event-Driven Streaming APIs), then support for subscriptions (the GraphQL name for live queries) will have to be added.
Architecture of a basic GraphQL backend
All of this results in a lot of boilerplate, server-side code and complexity like passing context around for data sharing and handling relationships between different entities in the result set, etc.
Community tooling for GraphQL addresses this complexity, especially when dealing with popular relational or NoSQL databases. They do this in one of two ways:
- Beginning with the database: auto-generating a ready-to-use GraphQL server from your new or existing database schema. If you need to extend this schema to include other unsupported data sources, you will have to do so manually. And, there are some GraphQL tools on the market that work with Postgres databases.
- Going in the opposite direction and beginning with a GraphQL schema: if you handwrite a GraphQL schema, the database schema and the resolver code can be auto-generated. A potential downside of such a solution is the possibility of opaqueness at the database layer, and therefore not being able to take advantage of the underlying database.
Let's say you have a standalone GraphQL server up and running. Now where exactly does this component fit into your back-end architecture? A few questions will need to be addressed:
- How will the GraphQL server interact with authorization middleware (new or existing)?
- If needed, how are multiple sources of data stitched together? The relationships between different sources of data may also need to be defined.
- What's the best way to handle a mix of REST and GraphQL APIs to support your front-end?
- What's a clean way to handle business logic (pre and post CRUD)? This is especially critical when you want to migrate a REST API that tightly couples business logic with the CRUD operations (using code or database stored procedures, etc.). For example, how do you handle the following story:
New signup request -> Validation -> Insert user details in DB -> Send welcome email
- How does your backend scale with GraphQL APIs?
The answers to these questions vary greatly depending on your existing architecture and the GraphQL tools you decide to use. Broadly speaking, however, the following are good guidelines to stick to:
- Front your REST and GraphQL APIs with an API gateway.
- Use a proxy to stitch different GraphQL schemas together (or to leave the option open to do so in the future).
Stitching together different GraphQL schemas using a GraphQL proxy
- Handling business logic (Pre/Post CRUD): you may have to execute some business logic before or after interacting with your database. You can choose either one or both of these patterns, depending on your use-cases:
- Write custom GraphQL APIs that execute business logic along with CRUD (need to write back-end code).
- Separate CRUD and business logic — subscribe to changes in data models and then execute business logic (need subscriptions support).
- Handling authentication: depending on how your client apps interact with your GraphQL API, you can handle authentication by reusing your existing middleware in the following ways:
- Clients directly call your API: Use a webhook to delegate auth to your existing middleware. Your GraphQL API can pass the auth tokens from your client to a webhook that interacts with your middleware and returns sessions variables.
- Clients contact your GraphQL endpoint via an API Gateway: If you aren't already doing so, this is a good opportunity to centralize your session resolution, benefiting not just your GraphQL API, but also every other service in your back-end.
- Scalability: Your GraphQL server should never become a bottleneck for scale or performance. It should scale linearly with more back-end resources. Design and engineer your server accordingly to leverage the underlying infrastructure and optimally interact with your databases.
Poorly written GraphQL implementations are prone to running into the n+1 problem — superfluous calls to the database for nested objects, a problem well-known in the ORM world too. For example, when you want to fetch a list of authors and each of their respective articles, an inferior implementation will make one query for a list of n authors and then n queries for each author's articles.