Yeah, But Why? Understanding GraphQL and How it Fits Into Your Product Stack
By now, it’s likely that you’ve heard the term GraphQL bandied about in conversations with other developers.
Much like any new, cool technology, it’s become that next big thing that “you have to be using.”
Well, perhaps. GraphQL is cool and it does have a place, but before you go whole hog into integrating it into your product stack, it’s important to understand what it is, why it’s important, and how it can help your product specifically.
What is GraphQL?
GraphQL stands for Graph Query Language.
At its core, this is what GraphQL actually is: a language for writing standardized data queries. Though the name is used to refer to a collection of conventions, during lunchtime nerd conversations we’re talking about this:
{
documents {
title
body
}
}
This is a GraphQL query. Though it may not look like it, this code is saying “I want back an array of documents and I only want the title
and body
field of each one." Translation:
{
"data": {
"documents": [
{
title: "Mark Zuckerberg's Big Day at the Beach",
body: "Little did Mark know, a wondrous world of whimsy was awaiting him..."
},
{
title: "Zuck's Xylophone Zoo",
body: "The sounds are brilliant and maddening at the same time! Ah, ha!"
}
]
}
}
What’s magical about that is that in theory, we can rewrite this exact same query to look like this without touching any backend code:
{
documents {
_id
title
createdAt
user {
name
emailAddress
}
}
}
If you’ve ever wired up the logic for making a user interface talk to a database, this probably intrigues you at least a little bit.
The reason why is that, albeit a tiny change — we’re still asking for an array of documents with certain fields — we’ve introduced a nested field, user
(here, the user who created the document).
Traditionally, when we want to pull off something like this, we need to do one of two things:
- Write an API endpoint or some server side logic specific to this exact query that returns each of our documents with a user in tow.
- Make multiple trips to our API/database and then combine the data on our client.
With GraphQL, we can skip all of this and get all of our data in one request. In other words, it allows us to ask for exactly what we need — and nothing else — when we need it without having to write excessively rigid logic on the server to fulfill those requests.
Enter the Schema
While this query language is a neat party trick, the real meat and potatoes backing this is on the server. In order to write GraphQL queries, we need to have a GraphQL server that understands them and can respond to them.
Though “GraphQL Server” may sound like the nickname Mark Zuckerberg uses for the machine that harvests youth from trick-or-treaters he kidnaps, it’s actually just a good ol’ fashioned HTTP server with an endpoint that accepts POST
requests.
More often than not, it lives at /graphql
where the server is defined like http://localhost:3000/graphql
or http://whatkidsofficer.zuckerberg.com/graphql
.
Attached to that server is what we call the schema. A schema is made up of two distinct parts, TypeDefs
and Resolvers
:
Type Definitions (TypeDefs)
Type definitions are detailed descriptions of the chunks of data in our application and the “possibilities” for interacting with that data via the query language we saw above. For example, you might have a User
type...
type Avatar {
url: String
altText: String
}
type User {
name: String
emailAddress: String
avatar: Avatar
username: String
}
or a Tweet
type that look like this:
type Tweet {
user: User
createdAt: String
tweet: String
retweets: Int
favorites: Int
}
What you’ll notice about types is that they can be intertwined. Here, we have a User
type that has a property avatar
set equal to a nested object type Avatar
. In the same vein, the Tweet
type below that has a user
field which points back to the User
type.
Resolvers
Resolvers are functions that respond to and “resolve” our requests, relying on the type definitions to understand what is and isn’t a properly formed response. Resolvers are just functions. When a request comes into our GraphQL server that matches a given resolver, that function is called and the logic it contains is executed.
A resolver comes in three different forms or “root” types: Query
, Mutation
, and Subscription
.
In order for a resolver to be recognized in our schema, it must be defined in two ways: on its root type and as the actual function that will resolve a request.
type Document {
_id: String
title: String
createdAt: String
updatedAt: String
body: String
owner: String
}
type Query {
documents(owner: String): [Document]
}
With our root Query
type defined, adding in our matching documents
resolver, our schema would end up looking something like this:
import gql from 'graphql-tag';
const schema = {
typeDefs: gql`
type Document {
_id: String
title: String
createdAt: String
updatedAt: String
body: String
owner: String
}
type Query {
documents(owner: String): [Document]
}
`,
resolvers: {
Query: {
documents: (parent, args, context) => {
return db.mongodb
.collection('Documents')
.find({})
.toArray();
},
},
},
};
In this example schema, the capital “Q” Query
in the typeDefs
string matches the capital "Q" Query
in the resolvers
object just beneath it. The documents
defined inside of type Query {}
? Yup, that matches the documents
defined in the Query
object inside of the resolvers
object.
The idea here is that everything we do in relation to our data is heavily typed and structured. As a beginner that may seem obnoxious, trivial, and excessive, but in practice the extra work we’re doing here is a serious boon on progress later.
To tie a nice bow around all of the above, we can introduce the other two additional root types beyond Query
: Mutation
and Subscription
. On the surface, the concepts for defining these are similar—it's only in the contents of their resolvers where we see differences.
import gql from 'graphql-tag';
const schema = {
typeDefs: gql`
type Document {
_id: String
title: String
createdAt: String
updatedAt: String
body: String
owner: String
}
type Query {
documents(owner: String): [Document]
}
type Mutation {
updateDocument(_id: String!, title: String, body: String): Document
}
type Subscription {
documentUpdated: Document
}
`,
resolvers: {
Query: {
documents: (parent, args, context) => {
// Just for example. `db.mongodb` is not defined on the context by default.
return context.db.mongodb
.collection('Documents')
.find({})
.toArray();
},
},
Mutation: {
updateDocument: (parent, args, context) => {
context.db.mongodb
.collection('Documents')
.update({ _id: args._id }, { $set: args });
// Just for example. `pubsub` is not defined on the context by default.
context.pubsub.publish('documentUpdated', args);
return args;
},
},
Subscription: {
documentUpdated: {
subscribe: (root, args, context) => context.pubsub.asyncIterator('documentUpdated'),
},
},
},
};
While we won’t get into the nitty gritty details of what’s happening here — we’ll save that for another post; seriously, too, subscribe here — what we can say is that the above is an example of a simple, but full GraphQL schema.
What we’re purposefully leaving out here is the client-side implementation. The reason why is that once you get to the client, how you go about running your queries is up to you. As of writing, the two most popular solutions are Apollo and Relay. Those are just fancy pants libraries that help speed up your consumption of a GraphQL API, though. You can just as well do an HTTP POST
request to your GraphQL server and rough it like a real burly motherfucker.
Okay, so, why is GraphQL important again?
Cute GIFs of Zuck being a creep and some technical overview aside, it may not be immediately clear why any of this is important. Honestly, it just looks like a bunch of extra code. Isn’t it?
Well, yes and no.
What you’ve likely noticed about GraphQL and its companion hype cycle is that it was quickly adopted by large organizations (and not just of the “you wear socks with sandals, too?” variant). We’re talking serious gangsters like: Airbnb, The New York Times, and Ticketmaster just to name a few.
What those folks “got” about GraphQL goes beyond the cost of the initial investment to get started with it. A totally dorky, context-specific explanation might read as “GraphQL is to REST what React was to building UIs with JavaScript.”
It’s the adult who comes in with a clipboard, starts assigning chores, and really bums everyone out until they’re 30 when they realize “ah, yeah, that was good advice — I like having a clean house.”
It gives us structure
If you’ve written any sort of API before — be it a REST API or an internal, ORM based one — you’ve likely hit a certain size of app where things got a bit squirrely if you didn’t do lots of thinking up front. We’ve all had to add that extra endpoint for that weird request that one dude in Arizona needs for his marketing report. Or, less romantically, we’ve all been the lazy goober who adds an extra publication — haaay Meteor folks — just to make our UI work and be done for the day.
It gives us a standard language for interacting with data (and our team)
Gone are the days of having to learn multiple languages just to query some data. One app might use a special database adapter while another just does HTTP requests using “some package a freelancer used once a few years back and we just kind of left it.”
Instead, GraphQL gives us a single language to use for writing queries and interacting with data that’s platform agnostic.
Where this becomes even more valuable is in a team context. Instead of everyone having to learn a bunch of different tools and techniques or interpreting your “yeah, I spent like 12 seconds on this contrived example” explanation, they can use a dirt simple syntax to communicate their wants/needs (for front-end folks) and their possibilities (for back-end folks).
A nice way to think of it is that GraphQL is kind of like the Esperanto of data but with more realistic goals of adoption.
It allows us to interact with multiple data sources
Similar to the above but different enough for a special mention, perhaps one of the neater uses of GraphQL is to standardize communication with multiple data sources. Depending on what you’re building (and with whom), you may find yourself needing to support a legacy MySQL database, and old SOAP API that’s crumbling like a witch in daylight, or some hip new blockchain doohickey.
While you’re still responsible for bringing the adapters, GraphQL — and more specifically, your schema — will avoid you having to rewrite validation for all of those different data sources. Not to mention, giving you a single, consistent way for interacting with them via resolvers. Hoogah.
It gives us more opportunities to improve performance
While no technology has ever truly delivered on its promises of Performance in a Box™, GraphQL does gives us the potential to significantly reduce our chugging of data and bandwidth to get at that data.
Like we hinted above, a well-written GraphQL query can scope down a bundle of HTTP requests into a single request. That’s nothing to sneeze at, y’know?
Further, if you’re used to an all the time real-time platform like Meteor, GraphQL’s shift to only allowing real time updates via subscriptions makes your dependence on the technique much more conscious.
It makes consuming data from multiple clients a breeze
If you’re building multiple apps that rely on the same data, GraphQL is a dream come true.
For example, say you’re writing an app like Twitter. On the desktop client, the increase in real estate gives your designers room to add more information on screen. On a mobile client, though, real estate is at a premium and so they use less information.
With GraphQL, we no longer have to have web or mobile specific API endpoints or fuss with sending more data than is necessary for the client consuming it. We just ask for what we need in that exact context and let our GraphQL schema take care of it for us.
Why shouldn’t I use GraphQL?
While we’ve spent the bulk of this post low-key convincing you why you should use GraphQL, it’d be irresponsible of us to join the bandwagon and leave out some counterpoints.
You’re a noobie to web dev
Getting real: GraphQL is complex. Now, I’m sure some dweeb in San Francisco just groaned at that sentence, but it’s true. Compared to vanilla HTTP requests or something like Meteor’s publications and subscriptions, it has more moving parts.
If you’re a total noob to web development, it’s probably best to avoid GraphQL for now. Instead, it’d be good to learn the fundamental means for reading data into your application like HTTP requests. It’s not glamorous and you won’t get carried out of a meetup on anyone’s shoulders, but you will learn why something like GraphQL has merit.
You’re just looking to waste time
Because GraphQL comes with several moving parts, it can be a bit of a paradise for folks who like to tinker. Because it allows you to get so granular, you can potentially spend a lot of time really tuning your types and queries without much need to do so.
If you’re building an app that a small number of people use regularly, it’s likely going to be overkill unless that app has a ton of data running through it.
You want to wear the band’s t-shirt and know their songs
Look. I’ll level with you. In high school I had dreadlocks and bought some spike bracelets because I thought the pale kids would like me more. In a lot of cases, using GraphQL can be the smoking cigarettes outside the gymnasium of web development.
If you’re just trying to get attention and say you’re one of the cool kids, it’s probably best to save GraphQL for weekend projects and after work noodling instead of a proclamation to “rewrite everything!”
None of the above technical stuff made you wiggle in your seat
Safe to say, you can accomplish everything that GraphQL does using a traditional REST API. It will take you more work, certainly, but if you’ve spent a decent amount of time with a specific data system and feel totally comfortable and productive with it: keep chugging.
If you’re happy with your current set up and your livelihood doesn’t depend on knowing the latest and greatest, it’s best to just do your thing and get to GraphQL when you have some noodling time.
Go forth and GraphQL
There’s no doubt that GraphQL will play some non-trivial part in the future of building products. If you have the time, resources, and/or need, it’s definitely worth learning and taking a crack at building something with.
If you’re interested in getting a head start with GraphQL, the upcoming v2.0 release of our boilerplate for products, Pup, will be embracing it fully. Hop over this way to learn more about Pup and get on the mailing list for first dibs on the release.
Originally published at cleverbeagle.com on August 31, 2018.