GraphQL is here to stay. Whether you love it or hate it, there’s no denying it’s a powerful tool to have under your belt. In four short years, it’s become one of the most popular open-source query languages. Many top companies now incorporate it into their APIs.
The goal of this article is to get you comfortable with when and how to use GraphQL. We will learn how to set up a basic GraphQL server using Node and Express.
What is GraphQL?
GraphQL is a query language, an execution engine, and a specification. You can think of it as a “layer” that sits between your backend and your client.
As a Query Language
GraphQL allows the client to ask for exactly the data it needs, in exactly the shape it wants. Take the following profile page
To populate this page, we need the user’s name, photo, and most recent posts. A typical REST API might expose the following endpoint to fetch user information:
GET api/users/<user_id>
But this endpoint could return all the information for that user – including information we don’t need. We would get back a massive JSON object full of useless fields that we don’t end up using on this page at all.
Let’s say our REST endpoint expects the following request for the user’s posts:
GET api/users/user_id/posts
The client now has to make two round trips to the backend to populate this page.
GraphQL solves both of these issues by allowing the client to send a POST request to a single endpoint. The body of this request contains a stringified query object specifying exactly what data is needed:
POST api/graphql query { user(id: $user_id) { name photo posts { text } } }
data: { "user": { "name": "John Doe", "photo": "https://randomuser.me/api/portraits/men/7.jpg", "posts": [ { "text": "Hello World! This is my first post." } ] } }
This makes it super easy for front end developers to get the data they need, and also means fewer network requests and less unnecessary data loaded into the browser.
As an Execution Engine
The GraphQL execution engine is what makes this magic happen. It’s responsible for parsing, validating and processing the request and returning the relevant information. The GraphQL backend is composed of two main features: a schema and resolvers.
The schema dictates what queries are valid. We define a schema file for each field of a possible query. We also define a root schema that compiles all other schemas. The schema for our query above might look like this:
type User { name: String photo: String posts: [Post] } type Post { text: String } type Query { user: User }
The resolvers are functions that are responsible for gathering the data for each field. When the GraphQL server receives a query, it will first validate the query against the schema. It will then traverse down through the query fields, calling the resolver function for each field. A resolver for the root query above could look like this:
Query: { user: (root, args, context) => { return db.getUser(args.user_id).then(user) => user; } }
Root, args, and context are special arguments passed to every resolver by GraphQL.
Root is an object that represents the parent object of the current field (in this case, the parent of the User field is the root Query. A resolver for the Posts field would receive the resolved User object as its root.)
Args are the specific arguments passed to the field in the query (in this case, the user_id.)
Context is a mutable object that holds the execution context for each request and can be used to hold important global information like authorization.
As a Specification
GraphQL is a continuously evolving language, and an agreed-upon specification is necessary to define its characteristics and capabilities. The current spec can be found at https://graphql.github.io/graphql-spec/.
How to build a GraphQL Server Using Node.js and Express.js
Now that we’ve discussed what GraphQL is, let’s dive into the tutorial. While GraphQL servers can be written in any language, this tutorial will use NodeJS and assumes some familiarity with Node, npm, and Express. If you are not familiar with how to set up a Node server with Express, check out this article.
Prerequisites
You should have Node and npm installed on your computer. You will need Node v6 at least as this tutorial will use some ES6 syntax that is only supported in v6 and beyond. You will need to install express, graphql, and express-graphql into the root directory of your project.
Getting Started
mkdir my_project && cd my_project/ npm init
do all the npm setup stuff (you can just hit “Enter” for everything)
npm install express graphql express-graphql --save touch server.js
The express module will run our API server. The express-graphql middleware will mount our GraphQL server on a /graphql
HTTP endpoint.
In the server.js file, add the following:
var express = require('express'); var expressGraphQL = require('express-graphql'); var graphql = require('graphql'); var schema = graphql.buildSchema(` type User { name: String } type Query { user: User } `); var rootResolver = { user: () => { return {name: "John Doe"}; }, }; var app = express(); app.use('/graphql', expressGraphQL({ schema: schema, rootValue: rootResolver, graphiql: true, })); app.listen(3000); console.log('GraphQL server listening at localhost:3000/graphql');
We set up our basic express server to listen at PORT 3000, and use the graphql-express middleware to mount the GraphQL server at the /graphql
endpoint. We define a schema for our User and a top-level (or “root”) Query schema that will validate our main query. We also define our top-level resolver that will process the request.
Run the server
node server.js
and navigate to the graphql endpoint in the browser
localhost:3000/graphql
Because we set the graphiql
option on the main GraphQL configuration object to true
, we will see a sandbox that allows us to run queries directly from the browser.
In the top right corner, we can open the Docs to check that our schema is set up correctly.
The first thing we should see when we open the docs is our top-level query. Click “Query” to see what that schema looks like:
Inside our “Query” is our “User.” So far so good. Let’s check that our User schema is correct:
Our “User” has a “name” field that is a “String” type. Great! Looks like our basic schema is set up. Let’s run a query. We can do that in the sandbox on the left like this:
Note that the GraphQL sandbox auto-suggests field names for you. Here’s the query we are running – paste this into the sandbox and hit ▶
query { user { name } }
In server.js we wrote a very basic resolving function that simply returns a hardcoded user object with the name “John Doe”, so that’s exactly what the query returns. Let’s try to query for this user’s posts
query { user { name posts } }
What’s going on here? We never defined a Post
schema, and our User
schema only includes a name
field, so this is not a valid query. Let’s update the schema in our server.js file
var schema = graphql.buildSchema(` type Post { text: String } type User { name: String posts: [Post] } type Query { user: User } `);
Now restart the server and run the query again
We’ve successfully queried without getting an error. Notice that the GraphQL sandbox also auto-filled the missing text
field inside our Post
schema. But we aren’t getting any posts. That’s because we haven’t defined a resolver for our posts
field yet. We also need to define a better resolver for our user
field so we can fetch different users based on input. We’ll update our server.js file to the following:
const express = require('express'); const expressGraphQL = require('express-graphql'); const graphql = require('graphql'); // _db mocks out a simple database const _db = { users: [ { id: 1, name: "John Doe" } ], posts: [ { user_id: 1, text: "Hello World! This is my first post." } ] } // userHandler mocks out a simple ORM const userHandler = { getUser(id) { for (let i = 0; i < _db.users.length; i++) { if(_db.users[i].id === id) { return _db.users[i]; } } }, getPosts(userId) { const userPosts = []; for (let i = 0; i < _db.posts.length; i++) { if (_db.posts[i].user_id === userId) { userPosts.push(_db.posts[i]); } } return userPosts; } } const schema = graphql.buildSchema(` type Post { text: String } type User { name: String posts: [Post] } type Query { user(id: Int!): User } `); const rootResolver = { user: (args) => { const user_id = args.id; const user = userHandler.getUser(user_id); console.log(userHandler.getUser(user_id)) const posts = userHandler.getPosts(user_id); return { name: user.name, posts: posts } } }; var app = express(); app.use('/graphql', expressGraphQL({ schema: schema, rootValue: rootResolver, graphiql: true, })); app.listen(3000); console.log('GraphQL server listening at localhost:3000/graphql');
Run the following query:
query { user(id: 1) { name posts { text } } }
We’ve mocked out a very simple database and handler function, which can look up a user and their posts by userId
. By calling this handler function inside our resolver, we can get the data we need. In the real world, this handler might connect to an external Postgres database using the Sequelize ORM.
We were also able to pass an argument to our query and look up a specific user by their id. Notice that we added the argument to the user
field on the schema. We specify the type of the argument (in this case an Int
) and make it required a required parameter by adding !
Beyond the Basics
Mutations
Mutations are what GraphQL calls updates to data. We won’t go into too much detail here, but the implementation is similar to what we’ve already discussed for queries:
- define a schema for the mutation
- write resolvers for each field of the mutation. In this case, the resolvers would update information in the database instead of simply fetching it
- run the mutation from the client, passing the input to be changed as an argument
Microservices
The real benefit of GraphQL is the ability to stitch together many different APIs. The resolver for each field passes its own resolved result to its children as their root argument. We didn’t need to use the root argument in this example because we defined our resolver on the root. But as a next step, our postsResolver
function could access the resolved user
object as its root and use that user’s id
to send a request to a different API.
Apollo Client
Apollo is a frontend state management library for Javascript. It hooks up a front end client to a graphql endpoint, and handles fetching and updating data, state management and caching. The main benefit of Apollo is the ability to embed graphql queries directly into frontend components (such as React components) and run only the query needed for that particular chunk of the UI.
When Should I Use GraphQL?
GraphQL is certainly a powerful tool, but like any tool, it isn’t always necessary. You wouldn’t use a chainsaw to slice a loaf of bread. If your application is relatively simple, you probably won’t need the added weight and complexity of GraphQL. However, if you are dealing with a complicated microservice architecture that pulls information from many different APIs, GraphQL can act as a “gateway” that simplifies and optimizes communication between the client and multiple backend services.
Takeaways
If your team leverages a microservice architecture, or you are looking for a more efficient way for front end devs to fetch data from many sources, it’s worth checking out GraphQL. Combined with a centralized API hub, it can boost your team’s productivity and greatly simplify your data flow.
Check out RapidAPI for teams to learn more about API hubs and how they can benefit your team today.
Leave a Reply