When I first heard about GraphQL, I was unconvinced about its usefulness or applicability to the way I write web applications. I had a good grasp on RESTful API design and implementation, and GraphQL didn’t seem like something I’d really “need”. Having had a bit more exposure to GraphQL, though, I’m really starting to see a lot of the benefits of the technology. In this post, I’ll try to outline some of the core concepts of GraphQL, and to explain the problems it seeks to solve.
What is it?
Here’s what the official GraphQL website has to say:
GraphQL is a query language for APIs and a runtime for fulfilling those queries with your existing data. GraphQL provides a complete and understandable description of the data in your API, gives clients the power to ask for exactly what they need and nothing more, makes it easier to evolve APIs over time, and enables powerful developer tools.
Let’s break this down into pieces and elaborate a little bit.
A Query Language
GraphQL is a query language much like SQL that allows clients or consumers of your API to request the data they need. Much like you’d SELECT *
from some database table, you can run GraphQL queries and specify fields you’d like returned. The syntax of GraphQL looks a lot like JSON with only the left hand side filled in.
query getUser {
user(id: 1) {
email
firstName
lastName
}
}
Self Documenting and Versionless
At the heart of any GraphQL API is a strongly typed schema representing all of your data types, relationships, and queries. This schema serves as an annoted source of documentation around all operations and fields defined by your API. GraphQL also has first class support for gracefully deprecating specific fields and queries, so you can iterate on your API without disrupting your client applications.
Powerful Developer Tools
One of the biggest reasons I’ve boarded the GraphQL hype train is graphiql, an in-browser IDE for GraphQL APIs. Graphiql provides an extremely compelling development experience for exploring, working with, and developing GraphQL APIs. I cannot understand how much of a pleasure this application is to work with. Seriously, graphiql alone is make recommending GraphQL for your next API seem reasonable.
Why does it exist?
One of the things I think developers often gloss over when presenting or exploring new technology is the “why” behind the technology. Knowing what problems a language or library exists to solve or what it seeks to improve are powerful tools in understanding the technology itself.
GraphQL’s primary purpose is to reduce the number of network calls required to interface with your API. If you’ve ever worked with an API that required you to make one request, and then to use some data from that response to make another N requests, this is a problem GraphQL seeks to solve. By allowing clients the ability to request nested data structures or even run multiple queries in a single request, network overhead can be grealy reduced. In today’s landscape of mobile devices and potentially limited network connectivity, reducing network overhead is crucial for providing a globally high quality experience in your applications.
Now, you might be saying “wasn’t this possible with REST before?” and you’d be right about that. Yes - it has been possible to provide nested data structures and sparse field sets with REST using headers or query strings or unique endpoints. However, that’s just the issue. Any time a new requirement arises around data structure or response format, it requires developing a new endpoint or implementing a new query string. This introduces friction and implementation cost to your API and your clients. By using a GraphQL schema, you can allow your clients to define their data requirements on their own, and avoid friction in the form of custom endpoints and parameters.
GraphQL also reduces friction around API versioning and deprecation. Rather than maintaining multiple API versions, you can use GraphQL’s deprecated
directive to provide your clients with a warning and descriptive message when fields are deprecated. This is much more graceful than managing API documentation or actively supporting multiple API versions. You can also add entirely new fields and relationships without disrupting existing clients, since their queries will continue to work as expected regardless of new fields, types, or queries existing.
Core Concepts
Now that we’ve got some background on GraphQL, it’s time to dive into the core concepts that drive it.
Types
GraphQL is a strongly typed language, so it includes a simple but powerful type system for creating your schema.
Scalar Types
GraphQL has several natively available primitive types:
- String
- Int
- Float
- Boolean
- ID
Everything here should be familiar to you if you’ve worked with most common programming languages, but the ID
field is slightly unique. In GraphQL, a field of type ID
denotes a unique identifier that is not intended to be human readable - like a UUID string.
Object Types
GraphQL object types are how you can define custom data structures within your schema. A User type might look something like this:
type User {
id: Int!
email: String!
firstName: String
lastName: String
}
Once we’ve defined this type in our schema, we can reference it anywhere as a type. Queries can return a User
, fields can be of type User
, and so forth.
The !
syntax above denotes a given field as required.
Lists
A field can be defined as a list
using square brackets []
around the type of the list. This is similar to the List<string>
syntax in languages like Java or C# for example. In practice, this might look something like this:
type User {
id: Int!
email: String!
firstName: String
lastName: String
friends: [User!]!
}
The double !
syntax here is something you’ll see often in GraphQL list fields. What we’re doing here is saying “friends is a required list of Users, where each element is non-null.” So the list itself cannot be null, and each element of the list also cannot be null.
Other types
There are some other GraphQL types like interfaces, unions, and enums that exist, but they’re a little more advanced and not entirely necessary to get up and running with a basic GraphQL schema. You can set up inheritance structures and more advanced validation schemes with these structures, but they’re outside of the scope of this basic introduction.
Queries
Queries are named methods for fetching data. Your schema defines a set of queries, then any client or consumer of your API will be able to run those queries. Queries look a lot like functions in a typical programming language. They have arguments and a return type. Here’s a sample query for fetching our User
type by ID.
query getUserById(id: Int!) : User!
Then, as a client, I can call this query like so:
{
getUserById(id: 1234) {
email
}
}
When running the query, I can select only the fields I need to fulfill my needs. This is where GraphQL reduces network overhead and provides flexibility to its consumers.
Mutations
Mutations function almost indentically to queries, except that they result in data changes. While queries are used for the (R)ead part of CRUD, mutations power the (C)reate, (U)pdate, and (D)elete operations. To define a “create” action for our User type, we could define and run a mutation like so:
# Definition
mutation createUser(email: String!, password: String!): User!
# Running the mutation
{
mutation createUser(email: "test@test.com", password: "password123") {
id
}
}
Resolvers
Resolvers are function that tell your GraphQL server how to fulfill requests. They are the implementation of the logic behind queries and mutations. The great thing about resolvers is that they are totally agnostic of your programming language and data source. As long as your resolver returns a data structure that maps properly to your GraphQL schema, you’re all set. There are all sorts of server-side GraphQL implementations for Python, Ruby, Node, C#, or whatever language you prefer.
My typical server-side language of choice is Node, so here’s what a Node resolver might look like:
const UserResolver = {
Query: {
getPlayer(parent, arguments, context) {
const { id } = arguments;
return context.models.User.findOne({ where: { id } });
},
},
};
This resolver structure is one defined by a common server-side GraphQL environment Apollo Server, but you can see how we might build out this structure to define function calls and business logic for each query defined in our schema.
Getting Started
If this introduction to GraphQL has you excited and ready to try things out, definitely head over to Apollo Launchpad and get started with an interactive GraphQL sandbox environment. The official GraphQL website has a fantastic, detailed introduction document as well that goes much more in depth that what we covered here. Apollo’s website in general also has some great tooling around GraphQL and blog content, too.
The best way to get into GraphQL and to understand is definitely to build something, so go get your hands dirty with a Twitter clone or a to-do app or something with craft beer like every other developer!