GraphQL in JavaScript: An Introduction

A fundamental intro to GraphQL and how to use it with Express to serve your APIs

Ross Bulat
Mar 6 · 13 min read

This article acts as an introductory to GraphQL, an API query language and runtime, that assumes no prior knowledge from the reader. It breaks down what GraphQL is — and why now is the time to take notice. The benefits it has over traditional REST APIs will be covered, along with the fundamental concepts and tools to get started.

The official GraphQL documentation does a great job at introducing the GraphQL schema and query language syntax, so rather than duplicating the idea of a coding tutorial, this piece will build the reader’s understanding and comprehension of GraphQL.

GraphQL clients are commonly used with Express, where Express provides a single endpoint to access your GraphQL service (with middleware also being supported out of the box). We’ll be using the JavaScript implementation of GraphQL, GraphQL.js, alongside express-graphql, throughout this piece.

GraphQL is an open specification, and there are indeed other implementations in the realm of JavaScript (such as the Apollo Server) and other languages, that each come with their own characteristics and strengths. However, the GraphQL.js client this piece explores undoubtedly acts as the best entry-point into the query language, and establishes a strong foundational understanding that can then be expanded upon with other clients.


The Rise of GraphQL

GraphQL is quickly gaining momentum in adoption, and is fast becoming the preferred alternative to REST APIs to fuel the backend services of modern apps — both web and native. Weekly graphql downloads have doubled in the past 12 months alone (March 2019 — March 2020), being its strongest period of adoption since it was open sourced by Facebook in 2015, three years after its internal inception in 2012.

The benefits GraphQL offer over REST APIs are also making it an attractive option for JavaScript frameworks like React, that is basing a lot of its upcoming Concurrent Mode functionality on GraphQL, alongside the Relay GraphQL client for the framework. With React being the most popular JavaScript framework by far, this will have big implications with the adoption of GraphQL for the foreseeable future.

All this activity suggests that there has not been a better time to look into GraphQL. Luckily — or perhaps by design — a GraphQL service can be introduced to an application without disruption, due to two of its characteristics:

  • GraphQL services are decoupled from the client side, living on the server side. A GraphQL service can be developed in isolation, and ran alongside your traditional API services with a single endpoint — commonly being /graphql. It will not interfere with the rest of your app ecosystem.

As we have already ascertained, JavaScript frameworks like React are actively working towards production-ready GraphQL support baked directly into the framework, to boost data management capabilities in conjunction with state management (to offer things like concurrent preloading of components and ultimately quicker UX responses).

Even though we can fall back to fetch() at any point, its likely we won’t want to when these libraries are production ready. In any case, it is already very easy to play with GraphQL with production-ready apps and databases to test real-world use cases.

This benefit of easy integration via a single endpoint is only one of a few advantages that make GraphQL attractive — let’s now examine GraphQL in more detail to understand its other benefits.

The Advantages of GraphQL vs RESTful APIs

The component-based structure of JavaScript frameworks we opt to use today have highlighted that modularity is key for building and maintaining modern apps, for a number of reasons: Easier version control, the ability to split CSS, JSX and logic on a per-component basis, much less breaking changes and code conflicts, the idea of local state and ability to refresh a subset of content rather than the entire stage, etc.

Where we have enjoyed these benefits on the front-end, the backend has stayed relatively stagnant on the API side where the RESTful API model has been the most opted for solution to serve apps in recent times, with its stateless operations while assuming nothing about the platform from which the request originates being positive attributes to the API design. But RESTful APIs also come with their problems when used with modular apps being built today.

Some of those issues are as follows:

  • We often call multiple endpoints to fetch the data required for one page, screen or for a particular component tree.

What we have ended up with is a slightly verbose and long winded process of obtaining the data we need. As complexity builds on the front end, these issues are enhanced further. GraphQL takes a stab at fixing these problems, and does so with its most distinctive characteristic: its modularity.

GraphQL is a modular API; you get exactly what you ask for

Where we settle for the same response body every time with a traditional RESTful endpoint, with GraphQL we are free to only request the exact fields we require from an arbitrary number of resources.

For example, in a traditional API model we might call the following to fetch an article for a blogging app, along with the author details to display alongside it:

// fetch article
`/articles/get/${articleId}`, ...
// fetch author details
`/api/author/get/${authorId}`, ...
// fetch total articles by author
`/api/author/total/${authorId}`, ...

Although this can be considered good API design — we are indeed adhering to DRY principles and keeping our endpoints modular within the constraints of the RESTful model — this is a clear example of the aforementioned limitations.

With GraphQL queries (also termed operations) however, we are freed from these limitations by having the ability to not only fetch this data in one request, but also to choose a subset of fields to be returned. This is how that GraphQL query may look:

// GraphQL query to fetch content necessary to display articlequery Query_Article {
getArticle(id: articleId) {
title
datePosted
content
}
getAuthor(id: authorId) {
bio
fullName
profilePicUrl
}
totalArticlesByAuthor(id: authorId)
}

Without diving into the query language syntax at this stage, we can see that this query is grouping multiple pieces of data together, and further, defining the exact fields needed to be returned, such as the title of the article, and fullName of the author.

This can apply to any CRUD operation, whereby GraphQL could query a multitude of methods that handle insertions, updates, deletions, aggregations, etc, all within one request.

GraphQL also has automatic type-checking by design

On top of this, a GraphQL schema is fully typed, and therefore has automatic type checking by design. If your queries do not adhere to your schema, they will yield an error. GraphQL schema is introduced in the next section.

Because of this flexibility of being able to simply “query” to our API exactly what we want, versioning becomes no longer required, e.g. we are no longer required to version the entirety of an API. Instead, we can simply include additional methods or fields to leverage within your GraphQL service as your requirements evolve.

The modularity of GraphQL solves many issues you face with component-based development in an almost uncanny way; the two compliment each other very well, and this repeatedly becomes apparent during development.

Let’s next expand on the above example and explore how a GraphQL service is constructed.


GraphQL Schema and Resolver Overview

The way GraphQL knows what queries are available, and their potential return types, all boil down to GraphQL’s type system. Types are defined within a schema: an object written in “graph schema language” that ultimately defines the GraphQL service.

The Schema

A schema defines what queries, or operations in general, can be made to the GraphQL API in question. The return values of which will adhere to the same structure of the query itself. Here is a summary of how a schema reflects what operations are available to call:

“Graph schema language” is very similar to the language used to call GraphQL operations, also referred to as “graph query language”. This naming convention just gives us a means of differentiating between the two.

Here is an example of a basic schema for querying an author’s name via their unique ID:

# `Query` types expose fetching methods to the apitype Query {
getAuthorName(_id: ID): String
}

The Query type is a built-in type that is used for exposing API methods for fetching data. Mutation on the other hand is used for operations that change data — such as inserting, updating and deleting data.

In schema query language, ID is also a String, with the only difference being that ID is not supposed to be human readable. The ID and String types are two of a few basic types, or “scalar types”, that graph schema language supports. The other scalar types are Int, Float and Boolean.

A note on the basic types supported

All types are optional by default, with a value or null both being valid. Putting an ! after the type makes them required. In addition to this, wrapping a type with square brackets [] defines an array of a type. These are more commonly referred to as “list” types in graph schema language.

Consider the following schema to fetch all author names, that will return an array of Strings. Note that the array can be empty, as String is not accompanied with an exclamation mark:

# fetching all authorstype Query {
getAuthorNames(): [String]!
}

The Root Resolver

A schema defines an interface of an API, but does not implement it. So what we must also have is a resolver: an object that holds functions corresponding to schema queries that will “resolve” a value at runtime:

const resolver = {
getAuthorName: async (args) => {
const { _id } = args;
/* get author name from database */
return authorName;
},
};

This object is known as a root resolver — the object we plug into the GraphQL initialisation function alongside the schema, and other configurations.

The schema and resolver can be loosely compared to the header and implementation files of C programs. The schema being the API header that defines the structure and type definitions, and the resolver implementing those defined operations within the schema.

To demonstrate a schema and resolver together in a working GraphQL API, let’s run through some setup steps to spin up a local Express server, that will provide a /graphql endpoint to the API.


Express + GraphQL API Server Setup

To facilitate embedding a GraphQL API within an Express endpoint, the express-graphql package is used with the core graphql and express packages. Simply install them with yarn:

yarn add express express-graphql graphql

Setting up the express server is pretty simple to do. Here is a Gist with the basic schema and resolver function from above used to initiate a GraphQL API, at the /graphql endpoint:

Note the imports we’ve used to build the schema and initialise the API. Check out all the options of graphqlHTTP here on Github.

Workspace considerations

When building your GraphQL services, consider using nodemon for automatically restarting the server as changes are made. Simply run nodemon in place of node:

// start GraphQL server with nodemonyarn global add nodemon
nodemon server.js

For VS Code users, the GraphQL package provides GraphQL syntax highlighting, useful for when your schema expands and you wish to take advantage of more advanced tools that can import whole files of GraphQL schema.

Test Queries with GraphiQL IDE

Along with these tools, GraphQL also provide an IDE for testing queries in the browser, called GraphiQL. Notice that we enabled it in the above Gist within graphqlHTTP():

const app = express();
app.use('/graphql', graphqlHTTP({
...
graphiql: true,
}));
app.listen(4000);

The project is hosted here on Github, where the developers encourage anyone to include the tool within their GraphQL clients and tools. GraphiQL is very capable of running verbose queries and displaying complex objects, and is recommended to use when developing and testing APIs.

Running http://localhost:4000/graphql from the above Gist will bring up the GraphiQL editor in the browser, not dissimilar to the following illustration, with a far more complex schema:

Illustration of the GraphiQL tool running in the browser, used for testing queries and their responses.

Notably, GraphiQL should only be enabled when developing your API, due to the security and performance implications of running it. Not only does GraphiQL consume memory and processing power of your server if it is exploited, having the service publicly available also advertises to the world what technologies are being used and how your APIs have been structured, which is a major security concern.

To avoid these scenarios, use the NODE_ENV environment variable within graphqlHTTP:

const app = express();
app.use('/graphql', graphqlHTTP({
...
graphiql: NODE_ENV === 'development',
}));
app.listen(4000);

With your server now set up, you are free to build your schema and corresponding resolvers.

Understanding Queries and Schema

At this point of the article, with the fundamental understanding of GraphQL now covered, a deep dive into the query and schema language would be a great way to get a feel of how your schema is developed. However, the official documentation already does a fantastic job at introducing the schema and query syntax.

Deep dives of particular use cases will be published in future articles — there are many benefits of exploring a multitude of features of any language when being applied to a particular solution.

Turning to the official documentation, the Getting Started guide and Introduction to GraphQL walkthrough should now be a breeze with the fundamental concepts under the reader’s belt.


Working with GraphQL

The final section of this piece covers other features and practices to be aware of in GraphQL that I feel should be included in an introductory, starting with how communication is made to a GraphQL API via a fetch() request.

Communicating with GraphQL with fetch()

We covered earlier that a GraphQL API can be contacted through a simple cURL request, or fetch() request within JavaScript. With a REST endpoint, we would typically send some headers to a defined endpoint along with a body of stringified JSON. Not much changes with GraphQL:

  • Unlike REST APIs, the /graphql endpoint is consistent throughout your application. It could indeed be defined as a global constant.

The following example defines a query and fetches a response to get an author’s name, given an ID:

// communicating with the /graphql endpoint with fetch()const authorId = 'some_uid';const query = `query GetAuthorName($_id: ID) {
getAuthorName(_id: $id)
}`;

fetch('/graphql', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
},
body: JSON.stringify({
query,
variables: {
_id, authorId
},
})
})
.then(r => r.json())
.then(data =>
console.log(data)
);

For those planning to migrate from a RESTful API to a GraphQL API, a logical first step would be to replace your existing fetch requests with a GraphQL fetch request conforming to the same return values — where possible — for minimum data refactoring.

Handling errors with the `customFormatErrorFn` function

Error handling can be done through the customFormatErrorFn field of graphqlHTTP when initialising GraphQL within Express.

If an error does exist, the returning value of this object will be available through the error field of the GraphQL query response in question.

Here is how we can include customFormatErrorFn and retrieve error details:

// retrieving error messages and formatting a responseconst app = express();
app.use('/graphql', graphqlHTTP({
...
customFormatErrorFn: error => {
return ({
message: error.message,
locations: error.locations,
stack: error.stack ? error.stack.split('\n') : [],
path: error.path
});
}

}));
app.listen(4000);

This is an ideal place to format errors for your clients to handle.

Using Middleware with GraphQL Resolvers

Middleware functions allow further processing of requests, before or after an endpoint is reached, depending on the order you include your middleware functions. express-graphql supports middleware built-in, and provides the req object to resolver functions so they can access any additional fields middleware may provide to the request.

As an example, consider connecting to a MongoDB database with a connect() middleware function before the request, and a disconnect() middleware function after the request. We’ll export these two functions from a mongo.js file that handles the connection to a database:

The connect and disconnect functions can now be imported and added as middleware alongside your /graphql endpoint:

const mongo = require('./mongo')...const app = express();
app.use(mongo.connect);
app.use('/graphql', graphqlHTTP({
schema: schema,
rootValue: resolvers,
graphiql: true,
}));
app.use(mongo.disconnect);
app.listen(4000, () => {
console.log('Running a GraphQL API server at http://localhost:4000/graphql');
});

And finally, we can access the req object within your resolvers, now with the additional client and db fields attached by the middleware:

// fetching an author name from Mongo in a GraphQL resolverconst resolvers = {
getAuthorName: async (args, req) => {
const { db } = req;
const { _id } = args;

const author = await db
collection('authors')
.findOne({
_id: ObjectId(_id)
}, {
projection: {
name: 1
}
});
return author.name;
}
}

GraphQL integrates into your Express Middleware setup seamlessly, adhering to Express design language. Authentication and other popular middleware use cases, such as logging, are also very suitable to use with GraphQL.


In Summary

This has been an introduction to GraphQL, its fundamental concepts and how it can be integrated with Express. This article has aimed to give the reader enough foundational knowledge to jump into further documentation about the query language and begin writing GraphQL services.

My next article in the subject of GraphQL introduces Apollo Server and the suite of tools Apollo offer. Apollo Server and its accompanying tools are the industry standard GraphQL solution for production apps today. Check out the article at the following link:

More deep dives and tools will be included here as they are published!

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade

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