📣 This post originally appeared as What is GraphQL? on The Bearer Blog.
GraphQL is a query language for APIs and a set of tools that enable sending and processing queries. While originally developed at Facebook, it has since grown beyond the social network's specific use case. Essentially, the client describes the type of data they want, and the server delivers only that data. At Bearer, we use GraphQL internally to drive our dashboard.
You can even think of GraphQL as a layer that sits on top of your resources, such as databases and third-party APIs. Much like REST APIs act as a unified way for clients to request data, GraphQL takes it a step further. Clients describe the shape of the response, so they are never surprised by the data that comes back from a GraphQL server.
Benefits of GraphQL
REST has been the default choice for APIs for quite some time. So why change? GraphQL doesn't replace REST, but instead offers an alternative type of API technology. It us currently being used in production by companies like GitHub, Facebook, AirBnB, Shopify, and Bearer. Some benefits include:
- Clients receive only the data they ask for. Instead of endpoints that return full objects, the clients in GraphQL can specify just the properties they want. As a bonus, this results in lower bandwidth usage.
- Retrieve multiple resources in a single request. Let's say you have a social page with a list of the user's friends, but you also want friends lists for each of those users. GraphQL queries let you follow references between resources.
- A type system. Even if the language of your client or server isn't explicitly typed, GraphQL is. This adds another layer of assurance that the data the client expects is the available data.
- Introspection: GraphQL allows clients to inspect the schema through a technique called introspection. Special fields, such as
__schema
can be queried to provide detailed information about the API.
To get a better understanding of how it all works, it is useful to first look at the anatomy of a GraphQL API.
Anatomy of GraphQL
When we talk about using GraphQL, we're talking about clients talking to servers. The full specification includes many details on the language, but as an overview we can focus on three core parts: the schema, queries, and resolvers.
The Schema
GraphQL's schema is the core of the query language. It all centers around a detailed type system. The shape of all data, requests, and responses are defined as part of the system.
type User {
name: String!
id: ID!
friends: [User]!
}
The type system contains a variety of built-in types, like String
and ID
above. You can also define your own. In the example, we define a User
type. It contains a name of type String
. The exclamation point !
denotes a required field. We also define a field of friends
that contains a list of other User
s.
Queries and mutations
Clients interact with the server by sending queries and mutations. A query reads data, similar in use to a GET
request. A mutation writes data, similar to POST
, PUT
, or DELETE
. Relating queries and mutation to the HTTP verbs we're familiar with can be useful when thinking about
To perform a query, it first needs to be added to the schema.
type Query {
user: User
users: [User]
}
This is a special Query
type used to define the type of data we expose to clients. You can then query for a list of all users and their friends.
{
users {
name
friends {
name
}
}
}
Notice that we are only requesting the name
of each friend. GraphQL knows that friends
is a list, or array, of User
. This means we can request just a portion of User
, or if we wanted to, nest it further and request the friends of friends. The client can send this query to the server, where it is executed. The result, depending on the server implementation, is a JSON response that may look something like the following:
{
"data": {
"users": [
{
"name": "Sara",
"friends": [
{
"name": "Steve"
}
]
}
]
}
}
This works for retrieving data, but for creating or modifying we use a mutation. Mutations look similar to queries.
type Mutation {
addUser(name: String!): User
}
This mutation creates a new user and takes the user's name as a required argument. It then returns the User
type. To call this mutation, we can write a request like this:
mutation {
addUser(name: "Alex") {
name
id
}
}
Here we add a new user with the name of "Alex" and ask for the created user's name
and id
in the response. GraphQL lets you specify what parts you want back, even during writes. No need to query the user individually for the updated details. The response from the server will look something like this:
{
"data": {
"addUser": {
"name": "Alex",
"id": "1"
}
}
}
But how does our server know where the data is coming from? That is where resolvers come in.
Resolvers
Resolvers are GraphQL's way of mapping data onto queries and mutations. The data can come from anywhere. Databases, third-party APIs, or even static objects. How resolvers are implemented depends on the language and server, but they roughly look like this:
// In Apollo Server (Javascript)
const resolvers = {
Query: {
users: () => db.users.getAll() // Example db request
}
}
These resolvers, when configured with your schema, will map properties between the defined types in the schema and the properties on the returned object. Meaning, even if db.users.getAll()
returns more than the information requested when the users
query is called, the GraphQL server will only send the pieces that were requested.
Disadvantages
GraphQL is not without its disadvantages.
- Repetition: The type system increases boilerplate code. When combined with a typed language, developers may find that they are essentially rewriting existing types just for GraphQL. Some tools alleviate this, like
graphql-codegen
. This excess code is primarily a concern of the server. Clients aren't required to re-define the full type system. - Lack of built-in authentication conventions: GraphQL recommends moving authorization and authentication to a separate business-logic layer. In some cases, applications will use a mix of REST and GraphQL. REST to handle log-in flows, and GraphQL to handle the data fetching.
- Different error practices: Clients are accustomed, in HTTP, to receiving http status codes that describe responses. All GraphQL responses are status
200
. If a query is unsuccessful, the returned JSON will include anerrors
key instead ofdata
. - Lack of built-in caching: Since GraphQL requests all hit the same endpoint, standard client caching doesn't work as it does with REST. As a result, servers and clients need to rely on their own system to handle serving cached data.
Ecosystem
Perhaps the most alluring thing about GraphQL, and part of its fast adoption, is the ecosystem. Many projects adopt a modern, developer-friendly approach to setup and configuration. Tools like GraphiQL make exploring an API easy, and easy to document. Server ecosystems like Apollo allow users to get up and running easier, as well as allow large teams to scale their GraphQL usage. Other approaches, like Prisma aim to abstract away the database. There are even excellent examples of GraphQL-as-a-service, like Fauna.
Where to go from here
We've only touched the surface on what GraphQL can do. A great place to begin is the official site, and if you want more technical specifications the GraphQL spec is available for viewing. Platforms like Apollo and Hasura also offer excellent tutorials on core GraphQL concepts and their tools. To begin implementing GraphQL in your preferred stack try these libraries:
- graphql-ruby (Ruby)
- Apollo (Node.js)
- Absinthe (Elixer)
- Graphene (Python)
- graphql-go (Go / Golang)
Are you using GraphQL in your organization? Let us know by connecting @BearerSH and check the Bearer Blog for articles.