Building a Simple Express-based GraphQL Server Using graphql-yoga

Another great tool was recently released by the folks behind the Graphcool framework: graphql-yoga 🧘. It provides an abstraction on top of Apollo’s apollo-server-express, the Express-based version of their JavaScript GraphQL server library. With graphql-yoga it’s now easier than ever to quickly create a basic GraphQL server.

Setup

First, in a new directory, create a new npm project. Here the -y flag will populate the package.json file with default values:

$ npm init -y

And add graphql-yoga to your project:

$ npm install graphql-yoga

# or, using Yarn:
$ yarn add graphql-yoga

We’ll also install nodemon as a dev-dependency, which will allow to run our server and watch for changes:

$ npm install nodemon --save-dev

# or, using Yarn:
$ yarn add nodemon --dev

In your project’s package.json file, add the following start script:

package.json

"scripts": {
  "start": "nodemon index.js"
},

With this script in place, you’ll be able to simply run npm start to start your server and have it restart automatically when you make changes.

Barebones Server

Let’s now create a barebones GraphQL server inside an index.js file. In this first example, our server will have one field in its query type called whatsForDinner and the resolver for that field will return a string with a random food option.

index.js

const { GraphQLServer } = require('graphql-yoga');

const dinnerOptions = ['🍕', '🌭', '🍔', '🥗', '🍣'];

const typeDefs = `
  type Query {
    whatsForDinner: String!
  }
`;

const resolvers = {
  Query: {
    whatsForDinner: () => {
      const idx = Math.floor(Math.random() * dinnerOptions.length);
      const foodChoice = dinnerOptions[idx];
      return `Tonight we eat ${foodChoice}`;
    }
  }
}

const opts = {
  port: 7777,
  endpoint: '/graphql'
}

const server = new GraphQLServer({ typeDefs, resolvers, opts });

server.start(() => {
  console.log(
    `😄 Server running at http://localhost:${opts.port}${opts.endpoint}`
  );
});

Here are a couple of things to note:

  • We create a new GraphQLServer instance and pass-in an object with our type definitions, corresponding resolvers, and an optional object to specify options for the server. Here our options specify a different port than the default (4000) as well as a different endpoint.
  • We then start our server with the start method on our GraphQLServer instance. The method optionally takes a callback that will be called when the server starts.

Now if you run your server, the console will display a message indicating that the server is running on port 7777.

You can use an IDE like GraphQL Playground and point it to the endpoint at http://localhost:7777/graphql and you’ll now be able to run the following query:

query {
  whatsForDinner
}

And the response will look something like this:

{
  "data": {
    "whatsForDinner": "Tonight we eat 🍣"
  }
}

Our query and response in GraqhQL Playground

Simple Todo GraphQL Backend

Let’s now take what we’ve learned and create a more full-featured GraphQL server. We’ll create a server for a simple todo app use-case where we’ll keep an in-memory representation of our data in the form of an array.

Here’s the implementation of our server:

index.js

const { GraphQLServer } = require('graphql-yoga');

let count = 2;
let todos = [{
  id: '0',
  content: 'Buy milk',
  isCompleted: true
},
{
  id: '1',
  content: 'Cook some lobster',
  isCompleted: false
}];

const typeDefs = `
  type Todo {
    id: ID!
    content: String!
    isCompleted: Boolean!
  }
  type Query {
    allTodos: [Todo!]!
    Todo(id: ID!): Todo!
  }
  type Mutation {
    createTodo(content: String!, isCompleted: Boolean!): Todo!
    updateTodo(id: ID!, content: String, isCompleted: Boolean): Todo!
    deleteTodo(id: ID!): Todo!
  }
`;

const resolvers = {
  Query: {
    allTodos: () => {
      return todos;
    },
    Todo: (_, { id }) => {
      const todo = todos.find(x => x.id === id);
      if (!todo) {
        throw new Error('Cannot find your todo!');
      }
      return todo;
    }
  },
  Mutation: {
    createTodo: (_, { content, isCompleted }) => {
      const newTodo = {
        id: count++,
        content,
        isCompleted
      }
      todos = [...todos, newTodo];
      return newTodo;
    },
    updateTodo: (_, { id, content, isCompleted }) => {
      let updatedTodo;

      todos = todos.map(todo => {
        if (todo.id === id) {
          updatedTodo = {
            id: todo.id,
            // for content and isCompleted, we first check if values are provided
            content: content !== undefined ? content : todo.content,
            isCompleted: isCompleted !== undefined ? isCompleted : todo.isCompleted
          }
          return updatedTodo;
        } else {
          return todo
        }
      });

      return updatedTodo;
    },
    deleteTodo: (_, { id }) => {
      const todoToDelete = todos.find(x => x.id === id);

      todos = todos.filter(todo => {
        return todo.id !== todoToDelete.id;
      });

      return todoToDelete;
    }
  }
}

const opts = {
  port: 7777,
  endpoint: '/graphql'
}

const server = new GraphQLServer({ typeDefs, resolvers, opts });

server.start(() => {
  console.log(
    `😄 Server running at http://localhost:${opts.port}${opts.endpoint}`
  );
});

You can try it our and play around the API using GraphQL playground. For example, to delete the todo with ID 0:

mutation {
  deleteTodo(id: "0") {
    id
    content
  }
}

…or to create a new todo:

mutation {
  createTodo(content: "Take a little nap 🛌", isCompleted: false) {
    content
    isCompleted
  }
}

The server setup stays the same as our starting example, but here our type definitions and resolvers are beefed-up to allow for the CRUD operations related to todo items.

Notice how resolver functions can receive the arguments passed-in to the queries or mutations. With the above example, our resolvers ignore the first argument, which is called root and contains the result of the previous resolver. We’re instead interested in the second argument passed-in to every resolver called args and which contains the arguments passed-in. We’re making use of object destructuring to map the values to variables.

Conclusion

🧘 You now have a very easy way to spin-up a GraphQL server. You can even easily deploy it to a service like Zeit Now. Note that for anything more real-world, you’ll want to hook-up to a real database using something like Mongoose for MongoDB or Sequelize for Postgres.

  Tweet It
✖ Clear

🕵 Search Results

🔎 Searching...