How to Get Started With GraphQL

GraphQL, a powerful query language revolutionized the way you query and manipulate data through your APIs. It provides a type system that you can use to define a schema, which describes what data is available from your API. The client can explore this schema, and request only the data that they need.

Why GraphQL over REST?

This is fundamentally different from how traditional REST APIs work, where you have multiple endpoints to request a set of data. In a GraphQL world, you only have one endpoint, and the request defines what data you need.

Rest API vs GraphQL
REST makes two separate request, while GraphQL does one with a query

This solves two performance problems that are common issues with REST APIs. They are called under, and over-fetching. Imagine you need data about the list of available articles on your site, and you also need all categories. In REST, you would make two separate calls to request this. One to the /articles endpoint, and one to the /categories endpoint. This creates unnecessary HTTP requests, you have two instead of one because one request is not enough. This is call under-fetching.

Let’s change things around and say you need a single article this time. You initiate a request to the /article endpoint, and you get back all the information associate with an article. But you only care about a title, an excerpt, and a link. This creates over-fetching. You end up with more data than you actually need, which makes you use more bandwidth than you should.


How GraphQL works?

As pointed out in the beginning, GraphQL works by describing your data through its type system. Let’s say you want to make the frontend application able to query for articles. For this, you can define a new type that defines the available information:

type Query {
    articles: {
        id: Int,
        title: String,
        category: Int
    }
}
article.graphql
Copied to clipboard!

As you can see, GraphQL is heavily typed. This lets you easily avoid type-related bugs, and print useful, descriptive error messages to users who don’t use your API correctly. By default, GraphQL support the following types: String, Int, Float, Boolean, ID, but you can also define your very own types using the type keyword.

Based on this schema, the frontend can ask for the data they need, in the following format:

{
   articles {
       title
   }
}
Notice that we can also access id and category according to the schema, but we don't care about them
Copied to clipboard!

This request only specifies that a title is needed for the articles. The API based on this request can respond with a result that has the same structure in JSON format:

{
    "data": {
        "articles": [
            {
                "title": "10 Best Practices for HTML"
            },
            {
                "title": "How to Get Started With GraphQL"
            }
        ]
    }
}
You only get back the data you need
Copied to clipboard!

Setting Up the Project

To get started with GraphQL, let’s build a simple API in Express that can serve articles and categories associated with these articles. First, we want to create the base for the express server. To start out, create a new project and after running npm init -y, install the following dependencies:

npm i express graphql express-graphql

Apart from Express and GraphQL itself, you will also need the express-graphql package that will glue the two together. Also make sure you install nodemon, as a dev dependency. This will help us automatically reload the API when we make changes to it. Once everything installed, change your start script to the following in your package.json:

"scripts": {
    "start": "nodemon server.js"
}
package.json
Copied to clipboard!

This will use nodemon to start the server using server.js, so as a next step, create that file at the root of your project. I will be also using import statements throughout the project. In order to enable them, you want to add the following to your package.json:

"type": "module"
package.json
Copied to clipboard!

Using buildSchema to create the schema

Let’s change server.js around a bit, and see how you can create and run a simple Express GraphQL server, using the official tutorial of GraphQL:

var express = require('express');
var { graphqlHTTP } = require('express-graphql');
var { buildSchema } = require('graphql');
 
var schema = buildSchema(`
    type Query {
        hello: String
    }
`);
 
var root = {
    hello: () => {
        return 'Hello world!';
    },
};
 
var app = express();
app.use('/graphql', graphqlHTTP({
    schema: schema,
    rootValue: root,
    graphiql: true,
}));
app.listen(4000);
console.log('Running a GraphQL API server at http://localhost:4000/graphql');
server.jsThis version doesn't use "type": "module"
Copied to clipboard!

At the top, we start off by importing the necessary functions along with Express itself. The buildSchema function can be used to create a GraphQLSchema object. It uses the Schema Definition Language, or SDL for short. It describes the schema as a string.

You can see that we have a Query type, that has a single property, called hello. It is a string type. We also have a root variable, that has the same signature, as the schema itself. The function inside this object is called a resolver. It tells GraphQL, what should be the resolved value when the hello node is requested. All of this can be put together using the graphqlHTTP function. You can see that it also sets graphiql to true. This provides a graphical user interface for executing queries. If you run npm run start now, and head over to your localhost, you will be presented with the following:

Running queries in Graphiql
Head over to http://localhost:4000/graphql and request the hello node.

If you try to request hello, you will get back “Hello world!”. You’ve just created a very basic GraphQL API using Express! 🎉 But we are not done yet, let’s see how you can use a GraphQLSchema object to create the same thing.

Using GraphQLSchema to create the schema

Remove everything from your server.js and add the following instead:

var express = require('express');
var { graphqlHTTP } = require('express-graphql');
const { GraphQLSchema, GraphQLObjectType, GraphQLString } = require('graphql');
 
const schema = new GraphQLSchema({
    query: new GraphQLObjectType({
        name: 'Query',
        fields: () => ({
            hello: {
                type: GraphQLString,
                resolve: () => '🎉'
            }
        })
    })
});
 
var app = express();
app.use('/graphql', graphqlHTTP({
    schema: schema, 
    graphiql: true,
}));
app.listen(4000);
console.log('Running a GraphQL API server at http://localhost:4000/graphql');
server.js
Copied to clipboard!

We’ve removed the SDL string, and instead, we are using built-in objects such as GraphQLSchema and GraphQLString. If you rerun the query in graphiql, you will get the same result, except this time, a confetti is returned. You will also notice, we don’t need a rootValue inside graphqlHTTP, because we’ve already defined the resolver, right next to the type of the field.

The differences between buildSchema vs GraphQLSchema

So what is the difference between buildSchema and graphQLSchema? Seems like using buildSchema is much more simpler, than doing it the other way. What are the differences?

Basically, when you use buildSchema, you also get back a graphQLSchema object. Given that you get the same output with both solutions — if you define the same schema — there’s no performance difference between the two. However, if you take it into account that buildSchema also needs to parse the SDL, the startup time is slower than using graphQlSchema objects.

However, there are other limitations as well. Does that mean that you should be using GraphQLSchema instead of buildSchema? The answer is no. There’s a third way that lets you write your schema in SDL, and separate out your resolvers as well. Enter the world of graphql-tools. We want a project structure, something similar to this, where everything is nicely separated:

The project structure we are looking for a scalable GraphQL API
And graphql-tools let’s you do just that

Writing Your Very First Types

To get more familiar with the Schema Definition Language, create an index.graphql file in your project, under a graphql folder. Make sure you have the GraphQL extension installed in VSCode to get syntax highlight. In your index file, create a Query type that will list all the available fields in your API:

type Article {
    id: Int
    title: String
    category: Int
}

type Category {
    id: Int
    name: String
}

type Query {
    """Get a list of articles"""
    articles(categoryId: Int): [Article]
    article(id: Int!): Article
    categories: [Category]
    category(id: Int!): Category
}
index.graphql
Copied to clipboard!

You can see we have introduced three different types. One for an article, one for a category (that you can reuse many times), and one for a query that we can use to query either multiple articles, a single article, or categories. To tell GraphQL we want to return these types — in either arrays or as a single object — we can pass them down to the Query type. Also note we can have arguments for a given field, using parentheses. By also appending an exclamation mark after the type, you can specify that the parameter is mandatory. For example, the article fields also requires and id.

You also have the ability to add a description to your fields using triple quotes. This will be shown in the documentation of graphiql.

The documentation on graphiql

It would be insane, however, to write every type in one single file. You can already see it will quickly become unmanageable. Instead, separate the article and category type out to different files. Lucikly for us, graphql-tools supports the use of the #import expression. Replace the two types with these import statements at the top of your index file:

#import './article.graphql'
#import './category.graphql'
index.graphql
Copied to clipboard!

And now we have the schema ready for us to query. But we don’t have any resolvers that would tell us what to return for them.


Creating Resolvers

For this, create a new folder called resolvers and add an index.js file that can export all available resolvers. Because of the scope of this tutorial, right now we only have one for the Query:

import Query from './query.js'

export default {
    Query
};
index.jsThe index file that exports all available resolvers
Copied to clipboard!

So what is inside query.js? Well, it simply exports an object with the same signature that we defined for our Query type:

export default {
    articles: () => null,
    article: () => null,
    categories: () => null,
    category: () => null
}
query.js
Copied to clipboard!

All resolvers currently return null. This is also the default behavior if you are missing a resolver for a field. This is where you would query your database and return the relevant values. For the sake of simplicity, I’m using the following objects as a placeholder for the database. This is what we will query:

const categories = [
    { id: 0, name: 'HTML' },
    { id: 1, name: 'CSS' },
    { id: 2, name: 'JavaScript' }
];

const articles = [
    { id: 0, title: '10 Best Practices for HTML', category: 1 },
    { id: 1, title: 'How to Get Started With GraphQL', category: 3 },
    { id: 2, title: 'How to Use Express to Build a REST API', category: 3 },
    { id: 3, title: 'How to Create Skeleton Loaders in CSS', category: 2 },
    { id: 4, title: 'What are Tuples and Records in JavaScript?', category: 3 }
];
query.js
Copied to clipboard!

We have 5 different articles, each with an id, a title and a category. We also have three different categories, each with an id and a name.

So what goes inside the function? Well, for the articles and categories, we have a relatively easy job, we just need to return the correct array:

export default {
    articles: () => articles
    categories: () => categories
}
query.js
Copied to clipboard!

But what if we want to provide a way for the client to also filter articles based on the id of a category? Remember that inside the schema, we’ve said that the API can accept a categoryId as an argument:

type Query {
    articles(categoryId: Int): [Article]
    ...
}
index.graphql
Copied to clipboard!

We can also access this argument inside the resolver, as each callback function can take in four different arguments. The second argument is what we are looking for, that holds the arguments passed to the query. So we can change the implementation to the following:

articles: (obj, args) => {
    if (args.categoryId) {
        return articles.filter(article => article.category === args.categoryId);
    }

    return articles;
},
query.js
Copied to clipboard!

This says that if a categoryId has been provided as an argument, it will filter the articles array for that categoryId. Otherwise, it will return the whole array.

We can do the same for the single article, and single category fields, using the find method. This leaves us with:

export default {
    articles: (obj, args) => {
        if (args.categoryId) {
            return articles.filter(article => article.category === args.categoryId);
        }

        return articles;
    },
    article: (obj, args) => articles.find(article => article.id === args.id),
    categories: () => categories,
    category: (obj, args) => categories.find(category => category.id === args.id)
}
query.js
Copied to clipboard!

Connecting Everything Together

All that is left to do is to connect these pieces together and test out the API. Go back to your server.js file and replace the previous implementation to instead use graphql-tools:

import express from 'express';
import { graphqlHTTP } from 'express-graphql'

import { loadSchemaSync } from '@graphql-tools/load'
import { GraphQLFileLoader } from '@graphql-tools/graphql-file-loader'
import { addResolversToSchema } from '@graphql-tools/schema'

import resolvers from './resolvers/index.js';

const typeDefs = loadSchemaSync('./graphql/index.graphql', {
    loaders: [new GraphQLFileLoader()]
});

const schema = addResolversToSchema({
    schema: typeDefs,
    resolvers,
});

const app = express();

app.use('/graphql', graphqlHTTP({
    schema,
    graphiql: true,
}));

app.listen(3000);

console.log('Server listening on http://localhost:3000');
server.js
Copied to clipboard!

The package provides a bunch of functions for making our lives easier. It provides a loader that can be used to load .graphql files, so we have our type definitions, as well as a function for adding the imported resolvers to the schema. For other ways of loading .graphql files, you can refer to the docs of graphql-tools. And again, this is passed to graphqlHTTP just as before. Note that I’ve also changed the path to /graphql, so this is where we will be able to access the API. And that is all this file does:


Testing the API in Postman

For testing, you can use graphiql as it is enabled, but to see a more real-world example, let’s see how you would send GET requests to the endpoint, using Postman. If you haven’t installed Postman yet, make sure you do it, and create a new GET request to http://localhost:3000/graphql. Make sure you also set the body to GraphQL, and let’s try to query a single article with a title and an article.

Querying a single article in GraphQL

And sure enough, we get back the expected data. If you only need the title, you can remove the field from the query, and there are no changes required to the backend API.

Querying fields in GraphQL
Notice that I get back a descriptive error message when I try to use an invalid argument.

Conclusion

Using this structure, you can clearly separate types and resolvers from each other (as well as different types and resolvers from one another), and make your API more scalable. However, do note there is no definitive project structure. Move things around, until they feel right, and use a project structure that you feel comfortable with.

If you would like to get the code of this project in one piece, you can clone it from the GitHub repository. There is an addition to the API to enhance single article entities, so make sure you check it out.

Have you used GraphQL before? Let us know your thoughts about it in the comments section down below! Thank you for reading through, happy coding!

How to Secure Your API With JSON Web Tokens
Remove ads
Remove ads

📚 Get access to exclusive content

Want to get access to exclusive content? Support webtips with the price of a coffee to get access to tips, checklists, cheatsheets, and much more. ☕

Get access Support us
Remove ads Read more on
Remove ads
Remove ads
🎉 Thank you for subscribing to our newsletter. x This site uses cookies We use cookies to understand visitors and create a better experience for you. By clicking on "Accept", you accept its use. To find out more, please see our privacy policy.