Photo by Daniele D’Andreti on Unsplash
Photo by Daniele D’Andreti on Unsplash

GraphQL: The Cure for REST APIs?

You are the disease and I’m the cure.

This article assumes a basic knowledge of REST APIs with Node/Express.

Web application programming interfaces (APIs) are an essential pillar of our connected world. Software uses these interfaces to communicate from applications to smartphones to deeply hidden backend servers, APIs are absolutely everywhere.

What Makes an API Great?

A great API should make it easy to do the right thing and make it really hard to do the wrong thing.

Any developer should be able to come to your API and understand with relative ease what they can achieve and how to do it. Our API should guide developers towards the best way to use the API and also push them away from bad practices, all that through the way we design it.

No matter what tool you use to build an API (REST, GraphQL, etc.), good API design should still be the first thought.

REST API Principles

Every HTTP request and response is made of two parts: the headers and the payload. The payload is familiar to anyone who knows the web. In those cases, the payload will be simple HTML displayed to the user. When we are working with APIs, the payload will be normally be JSON.

A REST API is a collection of endpoints where each endpoint represents a resource. So, when a client needs data about multiple resources, it has to perform multiple network requests to that REST API and then put together the data by combining the multiple responses it receives.

At its core an API is a data contract as an agreement on the general content of the request and/or response data. This contract is defined in the API documentation.

Check out Medium’s API documentation for a very thorough example.

The most common technique for handling API changes is to simply release a new, updated API in place of the old one. This is called versioning your API.

Some cases when versioning is necessary can be for example when you want to change the format of the response data, or when you want to change the response time. Another case will be when you want to remove part of the API.

There are different versioning methods available such as route versioning, query string versioning, and accept header versioning.

But if you’re not careful, you can release a new API that’s incompatible with existing API consumers. When this happens you are “breaking” current API clients — and that’s a bad thing.

These trouble spots can make it difficult to manage a REST API as it scales.

Principles of GraphQL

GraphQL is a specification for an API query language created by Facebook (Lee Byron, Nick Schrock, and Dan Schafer) and a server engine capable of executing such queries.

Before GraphQL went open source in 2015, Facebook used it internally for their mobile applications in 2012. It was built as an alternative to the common REST architecture which was encountering performance issues (> 1 billion users) with the ever changing and demanding mobile consumers.

For a history and technical break-down of GraphQL, grab some popcorn 🍿 and watch this presentation given by Lee Bryan at react-europe 2015:

To quickly summarize:

GraphQL is not specific to any backend or frontend framework, technical stack, or database. It can be used in any frontend environment, on any backend platform, and with any database.

  1. The client sends a query request to a single API endpoint.
  2. The GraphQL runtime layer accepts the text request and communicates with other services in the backend stack.
  3. That runtime provides a structure called a schema for servers to describe the data to be exposed in their APIs to put together a suitable data response.
  4. It sends that data back to the client in a format like JSON.

Another popcorn flick: 🎥

A GraphQL operation on the frontend is either a query (read), mutation (write), or subscription (continuous read). Each of those operations is only a string that needs to be constructed according to the GraphQL query language specification.

In a frontend web or mobile application, you can use GraphQL by making direct Ajax calls to a GraphQL server or with a client like Apollo or Relay (which will make the Ajax request on your behalf).

You can use a library like React (or React Native) to manage how your views use the data coming from a GraphQL service, but you can also do that with APIs native to their UI environments (e.g. DOM API or native iOS components).

Who is using GraphQL

Many well-known companies are invested in the GraphQL ecosystem due to the huge demand for modern applications.

Beyond Facebook, GraphQL is used by:

  • Github (2016)
  • Paypal (2016)
  • Credit Karma (2017)
  • Twitter (2017)
  • Yelp (2017)
  • Airbnb (2018)
  • Medium (2018)
  • Shopify (2018)
  • Lyft (2019)
  • MailChimp (2019)

GraphQL Advantages

Declarative Data Fetching

The client selects data along with its entities with fields across relationships in one query request. To retrieve all data in one request, a GraphQL query selects only the part of the data for the UI.

It offers a great separation of concerns: a client knows about the data requirements; the server knows about the data structure and how to resolve the data from a data source.

No Overfetching or Underfetching

What this means is the client has full control over which data it wants from the server. With RESTful APIs when you request a resource, you always get all that data from that resource which can lead to over-fetching.

If you want to fetch two different resources you need to make two separate calls to the server in a REST API. GraphQL lets you get many resources in a single request. This can solve under-fetching.

GraphQL for React, Angular, and Node
While Facebook showcased GraphQL on a client-side application with React, it is decoupled from any frontend or backend solution. The reference implementation of GraphQL is written in JavaScript, so the usage of GraphQL in Angular, Vue, Express, and other JavaScript libraries on the client-side and server-side is possible, and that’s just the JavaScript ecosystem.

Strongly Typed

GraphQL is a strongly typed query language because it is written in the expressive GraphQL Schema Definition Language (SDL). Being strongly-typed makes GraphQL less error prone, can be validated during compile-time and can be used for supportive IDE/editor integrations such as auto-completion and validation.

More flexibility to build developer tools

Having a type system makes it easier for building powerful developer tools such as Quell, Obsidian, Swell, GraphQL-Blueprint, AtomiQL, TorchQL, lexiQL, Peach QE, etc.

Managing REST Endpoints and Versioning

A common complaint about REST APIs is the lack of flexibility. As the needs on the client change, you usually have to create new endpoints, and those endpoints can begin to multiply quickly. Development speed can be slow because setting up new endpoints and versioning (e.g. api/v1/, api/v2/), often means that frontend and backend teams have more planning and communication to do with each other.

With GraphQL, the typical architecture involves a single endpoint. The single endpoint can act as a gateway and orchestrate several data sources, but the one endpoint still makes organization of data easier.

In GraphQL it is possible to deprecate the API on a field level. Thus a client receives a deprecation warning when querying a deprecated field. After a while, the deprecated field may be removed from the schema when not many clients are using it anymore. This makes it possible to evolve a GraphQL API over time without the need for versioning.

GraphQL Disadvantages

GraphQL Query Complexity
GraphQL doesn’t take away performance bottlenecks when you have to access multiple fields (authors, articles, comments) in one query. Whether the request was made in a RESTful architecture or GraphQL, the varied resources and fields still have to be retrieved from a data source.

Problems can arise when a client requests too many nested fields at once.

Frontend developers are not always aware of the work a server-side application has to perform to retrieve data, so there must be a mechanism like maximum query depths, query complexity weighting, avoiding recursion, or persistent queries for stopping inefficient requests from the other side.

GraphQL Rate Limiting

Whereas in REST it is simple to say “we allow only so many resource requests in one day”, it becomes difficult to make such a statement for individual GraphQL operations, because it can be everything between a cheap or expensive operation.

That’s where companies with public GraphQL APIs such as Github come up with their specific rate limiting calculations.

GraphQL Caching

Implementing a simplified cache with GraphQL is more complex than implementing it in REST. In REST, resources are accessed with URLs, so you can cache on a resource level because you have the resource URL as identifier.

The reason is that GraphQL operates via POST by executing all queries against a single endpoint and passing parameters through the body of the request. That single endpoint’s URL will produce different responses, which means it cannot be cached — at least not using the URL as the identifier.

Ways of caching that can be done:

  • On the client with Apollo Client and similar libraries, cache the returned objects independently of each other, identifying them by their unique global ID.
  • Using HTTP caching but using GET instead of POST for query requests. HTTP cache mainly works on GET because it only reads from the server it does not write to the server. Other HTTP methods (POST, PUT, DELETE) are declined to be cached, because doing so may lead to loss of data and wrong info being displayed.

Not necessary for small applications

If you are building a small full stack application that will not scale, it really doesn’t make sense to use GraphQL.

Let’s Start Using GraphQL

Server

At its core, building a GraphQL server mainly requires three main things:

  • A type system definition written in the Schema Definition Language (SDL).
  • A runtime execution engine to fulfill the requested queries according to the type system.
  • An HTTP server ready to accept query strings.

The order of operations on the server:

  1. The core GraphQL engine accepts the schema definition upon instantiation, builds the type schema, and allows you to execute queries against that schema.
  2. The HTTP server accepts the GraphQL queries and then passes them to the core GraphQL engine. When the engine responds, the HTTP server then passes the JSON response back to the client.

In GraphQL the concept used to fulfill data for a certain field is called a resolver. So the query that is received is traversed field by field and executes resolvers for each field.

At their core resolvers are really just simple functions:

function resolveName(parent, arguments, context) { 
return parent.name;
}

A resolver is in charge of resolving the data for a single field. The GraphQL engine will call the resolver for a particular field once it gets to this point.

The basic execution of a GraphQL query often resembles a simple depth-first search of a tree-like data structure:

At every step or node of a GraphQL query, a GraphQL server will typically execute the associated resolver function for that single field.

A resolve function usually takes 3 to 4 arguments. The first argument is usually the parent object. This is the object that was returned by the parent resolver function. If you take a look a the image above, this means that the name resolver would receive the user object the user resolver returned.

The second argument are the arguments for the fields. For example, if the field user was called with an id argument, the resolver function for the user field would receive the value provided for that id argument.

Finally, the third argument is often a context argument. This is often an object containing global and contextual data for the particular query. This is often used to include data that could potentially be accessed by any resolver.

To put this into context, let’s say we had this query:

query { 
user(id: "abc") {
name
}
}

The GraphQL server would first call the user resolver, with our “root” object (which varies based on implementations), the id argument, and the global context object. It would then take the result of the user resolver and use it to call the name resolver, which would receive a user object as a first argument, no extra arguments, and the global context object.

With a type system in place, and resolvers ready to execute for every field, we’ve got everything needed for a GraphQL implementation to execute queries.

But wait, just one more thing… ✋

Schema First vs Code First

Probably the biggest debate in the last few years, when it comes to implementing GraphQL servers, is whether a “schema-first” or “code-first” approach is best. These approaches refer to the way the type system is built on the server-side.

With schema-first, developers mainly build their schemas using the Schema Definition Language (SDL). Schema-first approaches usually let you define resolvers, by mapping them to fields and types defined using the SDL.

But there are always trade-offs.

The SDL itself provides no mechanism to describe the logic that should be executed when a field is requested. It makes sense, the GraphQL schema language is made to describe an interface, but it’s not a powerful programming language that we need to execute network calls, database requests, or any logic we usually need in an API server.

The fact that we need to separate the schema description and what happens at runtime can be a challenge with a schema-first approach. When a schema grows large enough, it can be a challenge to ensure the type definitions and their mapped resolvers are indeed valid. Any change in the schema must be reflected in the resolvers, and that is true for the opposite as well.

As schemas grow large and more and more team members contribute to the schema, it’s often useful to encapsulate schema definition behavior in reusable functions or classes rather than typing it out entirely, which opens the schema to inconsistencies.

With a code-first (often called resolver-first) approach the schema is defined and built programmatically. The design process begins with coding the resolvers and the SDL version of the GraphQL schema is a generated artifact (created with a script manually).

The schema-first approach is still considered a standard for right now, but that may change in the future … [insert suspense sound effect]

We will focus on a schema-first approach in this article.

express-graphql

is an express middleware that gets applies to our /graphql endpoint.

It requires an object to be passed to it which is the schema. You also need to install the express and graphql package.

const express = require('express');
const {graphqlHTTP} = require('express-graphql');
const { buildSchema } = require('graphql');
// GraphQL schema language (just a string):
let schema = buildSchema(`
type Query {
hello: String
}
`);

A schema string is passed to buildSchema which builds a GraphQL schema in memory. While having a schema is great, it needs the logic on what this hello field should return when a client requests it, which is where the resolver comes in by mapping the fields and types.

// Define a resolver object that can augment the schema at runtime 
const resolvers = {
Query: {
hello: () => {
return 'Hello Frank!';
}
}
};
let app = express();app.use(
'/graphql',
graphqlHTTP({
schema: schema,
rootValue: resolvers,
graphiql: true,
})
);

You can see the resolver hello function maps to the Query field hello that describes what gets returned to the client.

Since we defined graphiql: true in building the GraphQL runtime we can view it in our browser with the GraphQL explorer (i stands for interactive). GraphiQL provides an IDE experience with autocompletion, documentation and a simple query editor.

Let’s build out the schema with some very important kung-fu movie data. ❤

const { buildSchema } = require('graphql');const movies = [
{
title: '5 Deadly Venoms',
releaseDate: '9/12/78',
rating: 4,
},
{
title: '36 Chambers of Shaolin',
releaseDate: '5/1/79',
rating: 4,
},
{
title: 'The Chinese Connection',
releaseDate: '9/9/72',
rating: 5,
},
{
title: 'Druken Master',
releaseDate: '12/14/78',
rating: 4,
},
];
let schema = buildSchema(`
type Movie {
title: String
releaseDate: String
rating: Int
}
type Query {
movie: [Movie]
}
`);
const resolvers = {
Query: {
movie: () => {
return movies;
}
}
};
module.exports = {
schema,
resolvers
}

A schema needs to have resolvers for all fields and are responsible for returning a result for that field.

resolvers object has a map of resolvers for each GraphQL Object Type:

  • the movie resolver maps to the Query movie type which returns the movies array

apollo-server

Apollo is an open source implementation of GraphQL.

apollo-server is an open-source GraphQL server implementing graphql with a built-in Express server. So you only need to install this one package.

gql is a template literal tag that parses GraphQL queries into an abstract syntax tree (AST).

So here is what happens:

  1. Parses the schema string to an AST.
  2. Transforms the AST into a GraphQLSchema.
  3. Adds the resolver functions to the GraphQLSchema object.

So an AST is a representation of the schema document and the GraphQLSchema object is a data structure that can resolve GraphQL queries.

const { gql } = require('apollo-server');const movies = [
...
];
// GraphQL schema
// note: need to call your schema,
typeDefs
const typeDefs = gql`
type Movie {
title: String
releaseDate: String
rating: Int
}
type Query {
movie: [Movie]
}
`;
const resolvers = {
Query: {
movie: () => {
return movies;
}
}
};
module.exports = {
typeDefs,
resolvers
}

We create an ApolloServer and pass in the schema and resolvers object.

const {typeDefs, resolvers} = require('./schema.js')
const { ApolloServer } = require('apollo-server');
const server = new ApolloServer({
typeDefs,
resolvers
});
server.listen(3000)
.then(() => {
console.log(`🚀 Server ready at http://localhost:3000`)
});

Apollo Server uses Apollo Sandbox (an instance of Apollo Explorer) as its GraphQL IDE.

bing, bang. boom. 💥

Apollo Sandbox has some nifty schema auto documentation features too.

Be aware that just because we are using GraphQL, that doesn’t mean you can not use good ole’ Postman. But remember, GraphQL queries need a POST request.

There are other GraphQL Server tools beyond Express GraphQL and Apollo Server such as graphql-yoga, graphql-helix, but the first two are the most popular as of right now.

GraphQL Language

Let’s review some of the GraphQL language terminology.

Types — is an object, like a customer or order or product.

type Product {
name: String!
description: String!
price: Float!
...
}

Product is the type. Types are the building blocks of GraphQL.

The whole point of GraphQL, is to allow frontend developers to traverse a graph of connected objects. When you design your types, make sure that you have as many type-based references as possible.

For example, you’d want your customer type to refer to your orders type, and your order type to refer to your customer type:

type Customer {
orders: [Order!]!
}
type Order {
customer: Customer!
}

Note: ! means it‘s a required field.

Fields — is an attribute like name, description, price and so on. Fields belong to types.

type Product {
name: String!
description: String!
price: Float!
parentCategory: Category!
reviews: [Review]
...
}

Following the name of the field, you’ll find its data type. Data types can be scalars (primitives) of the following type:

  • Strings (String)
  • Integers (Int)
  • Floats (Float)
  • Booleans (Boolean)
  • Unique identifiers (ID)

Fields can also have a data type of another type within the same GraphQL schema. In the previous example, the parentCategory field points to a Category type. When you build your query, you can retrieve field values from the referenced types:

query {
product(id: "94695736") {
category {
name
}
}
}

This would return something like the following:

{
"data" {
"product": {
"category": {
"name": "Men's Belts"
}
}
}
}

Rather than having a singular value, GraphQL also supports fields having multiple values through the use of brackets ([]). A field defining a single String would be represented as follows:

type Product {
reviews: [Review]
}

Arguments — You can pass arguments to any GraphQL operation, and they are often used for retrieving specific nodes in the graph.

query {
product(id: "94695736") {
name
}
}

GraphQL requires both the parameter name and value, which in this case is id: “94695736”.

Variables — Are prefixed with a $ and are immediately proceeded by the data type and then an explanation point if it’s required. Queries can have an unlimited number of variables.

query orderHistory ($id: ID! $year: Int){}

This query requires the customer’s ID as the id variable and then optionally accepts year as an input.

This ain’t a book so check out the official GraphQL docs to learn more. 📖

Client

A GraphQL client is software that runs in each client (web browser, native application, etc.) that handles the life cycle of connecting to the GraphQL server, executing queries, and receiving responses.

Any code that allows you to make an HTTP request is a client, from cURL on the command line to any of the thousands of JavaScript libraries that are available. Every programming language, framework, operating system, and so on has the means of making HTTP requests because HTTP-based API calls underpin modern IT.

Formal GraphQL clients offer basic HTTP request handling plus:

  • Low-level networking — retry policies, limit on response sizes and timeouts.
  • Batching — group together multiple GraphQL queries.
  • Authentication — all clients require authentication with the GraphQL server before being able to execute queries.
  • Caching — can specify various cache directives for the type and field
  • Language-specific bindings — same functionality no matter what the programming language
  • Frontend framework integration — some frameworks are built entirely around GraphQL such as Facebook’s Relay.

All of the above can add up to a real-world advantage in programming time compared to just forming manual HTTP requests every time.

Apart from the making native JavaScript AJAX calls to a GraphQL server, the most popular GraphQL clients as of right now are graphql-request, Apollo Client and Relay.

graphql-request

is a super lightweight GraphQL client that works with any JavaScript web framework but does not have any caching or fancy features.

const {gql, GraphQLClient} = require('graphql-request');
const query = gql`
{
movie {
title
releaseDate
rating
}
}`;
const client = new GraphQLClient('http://localhost:3000/', { headers: {} })
client.request(query).then((data) => console.log(data));

Note: graphql-request still needs you to install the graphql package.

Since we are in Node.js we see the results in the terminal:

Apollo Client 🥇

As of right now, this the de-facto standard GraphQL library — works w/ React, Angular, Vue, etc.

The Apollo Client manages the complexity of orchestrating all queries. It is responsible for scheduling, optimizing, caching, managing state, and sending queries to a GraphQL-endpoint.

Be aware that Apollo Client is optimized for React as its view layer, so if you are not using React or any frontend framework, I personally suggest using the lightweight graphql-request.

We implement an ApolloClient client instance to do the GraphQL data fetching anywhere in our React app component tree. This is made possible by having the root App component a parent of the ApolloProvider component.

The ApolloProvider component leverages the React’s Context API to provide the client to any component that needs it.

import React from 'react';
import ReactDOM from 'react-dom';
import { ApolloClient, InMemoryCache, ApolloProvider } from
"@apollo/client"
import App from './App';
const client = new ApolloClient({
cache: new InMemoryCache(),
uri: 'http://localhost:3000/',
});
ReactDOM.render(
<ApolloProvider client={client}>
<App />
</ApolloProvider>,
document.getElementById('root')
);

In the App component we define the GraphQL query string with the gql template tag that parses into an AST. Then we render the data sent back from useQuery which also gives us two properties for handling loading and errors.

import { useQuery, gql } from "@apollo/client"const MOVIES =  gql`
{
movie {
title
releaseDate
rating
}
}
`;
function App() {

const { loading, error, data } = useQuery(MOVIES);
if (loading) return <p>Loading...</p>;
if (error) return <p>Error</p>;
const movies = data.movie.map(
({ title, releaseDate, rating }, index) => (
<li key={index} style = {{ lineHeight: "1.5"}}>
{title}, {releaseDate}, rating: {rating}
</li>
));
return (
<>
<ul style={{ listStyle : "none", paddingLeft: 5 }}>
{movies}
</ul>
</>
);
}
export default App;

And just like Apollo Server, we can use Apollo’s GraphQL IDE for testing.

And just like that, we created a GraphQL React client and a GraphQL API. 😊

GraphQL Resources

To learn more, there are a huuuuge amount of resources on GraphQL.

In case you didn’t get the movie quote in the article title, it is from one of the lesser known Sylvester Stallone movies. Def worth checking out.

--

--

--

Engineer, instructor, mentor, amateur photographer, curious traveler, timid runner, and occasional race car driver.

Love podcasts or audiobooks? Learn on the go with our new app.

Recommended from Medium

Block Device Guide | Basic Ceph Administration

Work Faster In Lightroom With a Trackpad

Concurrency and Avoiding Deadlocks

Dictionary in Swift

Comprehensive Guide on Successful NetSuite ERP Integration

DevOps for Small Organizations: Lessons from Ed

How to make a pop up menu with CSS

How to use Cython? — #8

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Frank Stepanski

Frank Stepanski

Engineer, instructor, mentor, amateur photographer, curious traveler, timid runner, and occasional race car driver.

More from Medium

How to Receive Webhook Events With Netlify Functions and JavaScript

Swagger — All you need to start

What is Long Polling — Learn The Basics and Fundamentals

VSCode extensions === (easier and more fun) dev life