If you're trying to build a simple GraphQL example using Node.js or Express.js to teach yourself the technology and you've been frustrated with other tutorials then you've come to the right place.

This tutorial focuses less on the setup of the server and more on how you actually structure your schema and resolvers which is the hard part to wrap your head around without clear examples.

Feel free to use my accompanying repo as an interactive reference while reading this article tylerbuchea/graphql-buildschema-example.

Setup

If you're like me you've setup a GraphQL project a handful of times but never get much further because no one is trying to show you the basic concepts that actually make the technology useful. Just returning "hello world" from graphiql isn't very valuable now is it?

So I'm going to assume you already have a server set up or can figure it out on your own. Here is my setup just for reference:

yarn add micro micro-dev graphql express-graphql jsonwebtoken

Then add "dev": "micro-dev" to your package.json "scripts"

const { buildSchema } = require('graphql');.
const server = require('express-graphql');

const schema = buildSchema(`
  type Query {
    hello: String
  }
`);

const rootValue = {
  hello: () => 'Hello world',
};

module.exports = server({
  schema,
  rootValue,
  graphiql: true,
});

Now run yarn dev

Real World Example with Relationships

I know I promised we wouldn't be doing a "hello world" example and we won't be. I just wanted to reference the technologies I was using so you had some context.

To elaborate a little more I am using the graphql library's buildSchema function to create my GraphQL API, which is a newer development in the library and is very similar to Apollo's makeExecutableSchema. I personally like buildSchema but if you end up using Apollo's version this tutorial should still help you grasp the common concept of schema/resolver structuring.

Below I'm going to lay out a simple Schema and Resolver setup that hits on all the main concepts of GraphQL so you'll be able to mimic those patterns in your own application and actually build something that can be used by a modern day app. The main piece most tutorials are missing is an example of querying for nested relationships which is what I'm going to demonstrate below. You should be able to extrapolate any number of relations after seeing a basic and realistic example of one.

const server = require('express-graphql');
const { buildSchema } = require('graphql');

// This could also be MongoDB, PostgreSQL, etc
const db = {
  users: [
    {
      organization: '123', // this is a relation by id
      id: 'abc',
      name: 'Elon Musk',
      password: 'password123',
    },
  ],
  organizations: [
    {
      users: ['abc'], // this is a relation by ids
      id: '123',
      name: 'Space X',
      phone: '555-555-5555',
    }
  ],
};

const schema = buildSchema(`
  type Query {
    users: [User]
    user(id: ID!): User
    organizations: [Organization]
    organization(id: ID!): Organization
  }
  type User {
    organization: Organization
    id: ID
    name: String
    password: String
  }
  type Organization {
    users: [User]
    id: ID
    name: String
    phone: String
  }
`);

class User {
  constructor({ organization, ...rest }) {
    Object.assign(this, rest);
    this.organizationId = organization;
  }

  organization() {
    const organization = db
      .organizations
      .find(({ id }) => id === this.organizationId);

    return new Organization(organization);
  }
}

class Organization {
  constructor({ users, ...rest }) {
    Object.assign(this, rest);
    this.userIds = users;
  }

  users() {
    return db.users
      .filter(({ id }) => this.userIds.includes(id))
      .map(user => new User(user));
  }
}

const rootValue = {
  users() {
    return db.users.map(user => new User(user));
  },
  user({ id }) {
    return new User(db.users.find(user => user.id === id));
  },
  organizations() {
    return db
      .organizations
      .map(organization => new Organization(organization));
  },
  organization({ id }) {
    const organization = db
      .organizations
      .find(organization => organization.id === id);

    return new Organization(organization);
  },
};

module.exports = server({
  schema,
  rootValue,
  graphiql: true,
});

With the structure above you can now perform complex queries that grab only the data you need in the structure you need.

You can try this out by running yarn dev, visiting http://localhost:3000, and then entering the query below.

query {
  organization(id: "123") {
    name
    phone
    users {
      name
      organization {
        name
      }
    }
  }
}

The query above outputs this JSON

{
  "data": {
    "organization": {
      "name": "Space X",
      "phone": "555-555-5555",
      "users": [
        {
          "name": "Elon Musk",
          "organization": {
            "name": "Space X"
          }
        }
      ]
    }
  }
}

Schemas Resolvers Classes

Prefer building a GraphQL schema that describes how clients use the data, rather than mirroring the legacy database schema. - Thinking in Graphs

Some of this code may seem redundent but that is the point of GraphQL. It allows you to extrapolate layers of your application so they are less tightly coupled. This structure would allow me to grab a root value say "organizations" from MongoDB but then easily pull the users associated for those entries from an entirely different database or even a REST endpoint.

By separating out the code into schema, class, and resolvers you have more control over changing your API in the future or adapting your current API for more practical applications.

The schema describes your dataset and types to the client and does some basic enforcement. It's an abstraction layer and does not need to match your database one for one. Because sometime pieces of the schema are pulled from multiple sources.

The classes abstract nested data fetching and more advanced type and error checking into an easily reusable and testable interface.

The resolvers expose root level queries and form the edges of your API that are exposed to clients. They can also perform additional tasks such as logging and can provide context such as authentication details, which I will cover later in this article.

Async

I know what you're thinking, "This is great for synchronous data fetching from a JavaScript object but what about when I want to call into my database or to a REST API?" Well I wouldn't leave you hanging! The answer is simple GraphQL can also handle promises and will resolve accordingly so you can use async methods as well.

Replace your organization class with the one below then run the query again. It should take five seconds to load this time because it is async but it will have all the same data.

class Organization {
  constructor({ users, ...rest }) {
    Object.assign(this, rest);
    this.userIds = users;
  }

  async users() {
    await new Promise(resolve => setTimeout(resolve, 5000));

    return db.users
      .filter(({ id })=> this.userIds.includes(id))
      .map(user => new User(user));
  }
}

Mutations

"Okay great", you might be saying, "But how do I mutate data?" Well you have probably heard about mutations in GraphQL and I'm going to let you in on a little secret. Mutations and Queries are exactly the same thing it is only a semantic difference for separating code that changes data from code that simply queries data. So given this knowledge we can easily create mutations with our prior knowledge.

Just add the following code and your ready to go. Just remember nothing is technically stoping you from putting the signup mutation under Query but it is bad practice by putting it under mutation you're signaling to anyone consuming your API that this operation will change the data.

const schema = buildSchema(`
  type Mutation {
    signup(organization:String, id:String, name:String): User
  }
  #...
`);

const rootValue = {
  signup({ organization, id, name }) {
    const user = { organization, id, name };
    db.users.push(user);
    return user;
  },
  //...
};

And you consume it just like you would a query except you just change the keyword to mutation:

mutation {
  signup(organization:"123", id:"newUserId", name:"Tyler Buchea") {
    name
  }
}

Errors

How do we handle errors? You simply throw errors and GraphQL will respond accordingly with a { errors: [...] } JSON object.

const resolvers = {
  signup({ organization, id, name, password }) {
    const user = { id, organization, name, password };
    const match = db.users.find(user => user.name === name);
    if (match) throw Error('This username already exists');
    db.users.push(user);
    return user;
  },
  //...
};

Authentication

Another thing I see lacking in most examples is JWT (JSON web token) authentication and how to handle it in a GraphQL server. It's very similar to how you'd do things in a REST API we simply place in a middleware that decodes the incoming request and attaches the decoded data to the request object then instead of operating directly off of the request object we pass it into express-graphql via the context object. Which is just an object that will get passed to all of our root resolvers.

module.exports = server(
  attachJwt(jwtSecret)(req => {
    return ({
      schema,
      rootValue,
      graphiql: true,
      context: { user: req.jwt },
    })
  })
);

function attachJwt(secret) {
  return function(fn) {
    return (req, res, next) => {
      const bearerToken = process.env.NODE_ENV === 'production'
        ? req.headers.authorization;
        : 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6ImFiYyIsImlhdCI6MTUzMjQ1NDE4MH0.U2IXOLpKqcPLCtzIasl_U8cK_I5tDMAW_CPN5szzhwA';

      if (bearerToken) {
        try {
          const token = bearerToken.replace('Bearer ', '');
          req.jwt = jwt.verify(token, secret);
        } catch(error) {
          console.log(error);
        }
      }
      return fn(req, res, next)
    }
  }
}

Now let's say we have a super secret query that we only want logged in users to be able to access. Keep in mind that all of our resolvers so far have been completely public. Now that we have a user context we can make our tellMeADadJoke query safe from the public.

const jwtSecret = 'makethislongandrandom';
const jwt = require('jsonwebtoken');

//...
const schema = buildSchema(`
  type Query {
    login: String
    tellMeADadJoke: String
    #...
  }
  #...
`);

const resolvers = {
  login({ username, password }) {
    const user = db.users.find(user => user.name === username);
    const incorrectPassword = user.password !== password;
    if (!user || incorrectPassword) {
      throw Error('username or password was incorrect');
    }
    const token = jwt.sign(payload, jwtSecret);
    return token;
  },
  tellMeADadJoke(data, { user }) { // this second bit is "context"
    if (!user) throw Error('not authorized');
    return 'If you see a robbery at an Apple Store does that make you an iWitness?';
  },
  //...
}

We've hard coded the JWT token into the middleware above to make this example easy to test but usually you'll have to login first and get yourself a JWT token then apply it to the request to get back the joke you want.

query {
  login(username:"Elon Musk" password:"password123")
}

Then you can:

query {
  tellMeADadJoke
}

Unfortunately there is no easy way to attach HTTP Authorization headers to GraphiQL so you'll need to bake your JWT token into a development environment variable or use an alternative that handles header such as brew cask install graphql-ide.

You can also follow the open issue on the GraphiQL Github repo where some users are asking for this functionality out of the box https://github.com/graphql/graphiql/issues/59.

Conclusion

This post ended up being much larger than I intended but I hope it gave a more practical view of how to implement the essential aspects of any modern API in GraphQL.

Please let me know if I missed anything in the comments below and thanks for hanging in there with me. 🎉

Resources