Tag Archives: graphql

Set up GraphQL with gqlgen and golang

The goal: setting up GraphQL server using Gqlgen library.
We are going to set up GraphQL server with Users, and create queries to retrieve the users by id or user name.

Schema-first approach – means that instead of using library apis (code-first approach) we are going to write our schema manually using the GraphQL schema definition language.

Setting up the project

  • Create project directory
    mkdir gqlgen-tutorial
  • Navigate to the folder
    cd gqlgen-tutorial
  • Initialize go project.
    go mod init gqlgen-tutorial
  • Create tools.go and add gqlgen  library
//go:build tools
// +build tools

package tools

import _ "github.com/99designs/gqlgen"
  • Add the library
    go mod tidy

Initializing Gqlgen with boilerplate schema and resolvers

gqlgen has handy command to Initialize gqlgen config and generate the models.

go run github.com/99designs/gqlgen init

This will create server.go the server starting point and ./graph directory with a couple of files including schema.graphqls and if you open it you will see that it comes with pre-defined example schema which we are going to remove later and start from scratch.

graph/model/model_gen.go – is auto generated file containing structure of defined by the schema file graph/schema.graphqls

graph/generated.go – this is a file with generated code that injects context and middleware for each query and mutation.

We should not modify these files since they will be modified by gqlgen as we update the schema. Instead, we should edit these files:

graph/schema.graphqls –  GraphQL schema file where types, queries, and mutations are defined.

graph/resolver.go – resolver functions for queries and mutations defined in schema.graphqls

At this point we could start the server and see the playground and the schema.

go run ./server.go

Sometimes you might see an error and in this case just run go mod tidy again.

Defining queries

First let’s remove boilerplate schema from graph/schema.graphqls and add our new schema

graph/schema.graphqls

# GraphQL schema example
#
# https://gqlgen.com/getting-started/


type User {
  id: ID!
  name: String!
  userType: String!
}

type Query {
  getUser(id:ID!): User
}

input NewUser {
  userId: String!
  userName: String!
  userType: String!
}

type Mutation {
  createUser(input: NewUser!): User!
}

 

Generate code and running the API

We will now generate code, which will update the following files using the information we provided in the schema file:

  • schema.resolvers.go
  • model/models_gen.go
  • generated/generated.go

Delete the example code in schema.resolvers.go and then run the following command:

go run github.com/99designs/gqlgen generate

If we run the server we will run into an error because we didn’t define any resolver yet.

Defining the backend to fetch and store values

In resolver.go:

– import qlgen-tutorial/graph/model.  line: 3
– declare a Hash Map that we will use to store users. Line 10

package graph

import "gqlgen-tutorial/graph/model"

// This file will not be regenerated automatically.
//
// It serves as dependency injection for your app, add any dependencies you require here.

type Resolver struct{
    UsersStore map[string]model.User	
}

 

We defined UserStore of type map which essentially is a hash-map with keys of type string and values of type model.User.

In schema.resolvers.go, we are going to modify the boilerplate methods: CreateUser and GetUser

package graph

// This file will be automatically regenerated based on the schema, any resolver implementations
// will be copied through when generating and any unknown code will be moved to the end.
// Code generated by github.com/99designs/gqlgen version v0.17.24

import (
    "context"
    "fmt"
    "gqlgen-tutorial/graph/model"
)

// CreateUser is the resolver for the createUser field.
func (r *mutationResolver) CreateUser(ctx context.Context, input model.NewUser) (*model.User, error) {

    // create new user to be added in r.Resolver.UsersStore and returned
    var user model.User

    if len(r.Resolver.UsersStore) == 0 {
        // create new UserStore hash map if it does not exist
        r.Resolver.UsersStore = make(map[string]model.User)
    }
    // set up new user attributes
    user.ID = input.UserID
    user.Name = input.UserName
    user.UserType = input.UserType

    // adds newly created user into the resolver's UserStore
    r.Resolver.UsersStore[input.UserID] = user

    return &user, nil
}

// GetUser is the resolver for the getUser field.
func (r *queryResolver) GetUser(ctx context.Context, id string) (*model.User, error) {

    fmt.Println(r.Resolver.UsersStore)

    // retrieve user from  the store
    user, isOk := r.Resolver.UsersStore[id]
    if !isOk {
        return nil, fmt.Errorf("not found")
    }
    return &user, nil
}

// Mutation returns MutationResolver implementation.
func (r *Resolver) Mutation() MutationResolver { return &mutationResolver{r} }

// Query returns QueryResolver implementation.
func (r *Resolver) Query() QueryResolver { return &queryResolver{r} }

type mutationResolver struct{ *Resolver }
type queryResolver struct{ *Resolver }

GetUser is pretty straight forward: it gets user id and returns the user from the hash map store.

CreateUser first checks if UserStore is initialized. Line 19. If CreateUser length is 0 it initializes the hash-map. Line 21.
Then it gets the values for username, id and userType from the input parameter and sets up the new user, stores it into the UserStore, line 29, and returns it.

 

Testing

Creating user

Mutation:

mutation createUserMutation($input: NewUser!) {
  createUser(input: $input) {
    name
    id
  }
}

Variables:

{
  "input": {
    "userId": "1",
    "userName": "John",
    "userType": "admin",
    "userGender": "male"
  }
}

Query for user with id

Query:

query getUserQuery($nid: ID!) {
  getUser(id:$nid) {
    id
    name
    userType
    userGender
  }
}

Variables:

{
  "nid": "1"
}

 

Breaking the schema into separate group of files.

Having the entire schema into the server.js file is not practical, especially if the schema grows bigger, so let’s move the schema into separate folders for easy management:

mkdir ./src/schema/queries -p
mkdir ./src/schema/types -p
mkdir ./src/schema/mutations -p

./src/schema/index.js

const graphql = require('graphql');
const queries = require('./queries');
const mutations = require('./mutations');

var rootQuery = new graphql.GraphQLObjectType({
  name: 'Query',
  fields: {...queries, ...mutations },
});

module.exports = new graphql.GraphQLSchema({
  query: rootQuery
});

./src/schema/types/index.js

let Dogs = require('./dogs.js');

module.exports = {
  ...Dogs,
};

./src/schema/types/dogs.js

const graphql = require('graphql');

module.exports = new graphql.GraphQLObjectType({
  name: 'dogType',
  fields: {
    id: { type: graphql.GraphQLString },
    breed: { type: graphql.GraphQLString },
    displayImage: { type: graphql.GraphQLString },
  }
});

./src/schema/queries/index.js

let Dogs = require('./dogs.js');

module.exports = {
  ...Dogs
};

./src/schema/queries/dogs.js

const graphql = require('graphql');
const dogType = require('../types/dogs');
const dogs = require('../../models/mock_data/dogs.js');

module.exports = {
  getDogByBreed: {
    type: dogType,
    args: {
      breed: { type: graphql.GraphQLString }
    },
    resolve: function (_, {breed}) {
      var result = dogs.find(function(dog){
        return breed == dog.breed;
      });
      return result;
    }
  } 
}

./src/schema/mutations/index.js

let Dogs = require('./dogs.js');

module.exports = {
  ...Dogs
};

./src/schema/mutations/dogs.js

const graphql = require('graphql');
const dogs = require('../../models/mock_data/dogs.js');

module.exports = {
  addDogBreed: {
    type: graphql.GraphQLString,
    args: {
      id: { type: graphql.GraphQLString },
      breed: { type: graphql.GraphQLString },
      displayImage: { type: graphql.GraphQLString }
    },
    resolve: function (_, {id, breed, displayImage}) {
      dogs.push({
        id: id,
        breed: breed,
        displayImage: displayImage
      });
      return "OK!";
    }
  }   
}

./server.js

var express = require('express');
var graphqlHTTP = require('express-graphql');
const schema = require('./src/schema');


// Logger middleware
var logger = function(req, res, next) {
  console.log("GOT REQUEST >", req.ip);
  next(); // Passing the request to the next handler in the stack.
}

var app = express();
app.use(logger);
app.use('/graphql', graphqlHTTP({
  schema: schema,
  graphiql: true,
}));
app.listen(4000);
console.log('Running a GraphQL API server at localhost:4000/graphql');

 

Setting up faster development environment using Nodemon and Babel.

Adding Nodemon

Nodemon is the same like node with the benefit that it will monitor the developing folder and restart automatically,, which makes the developing process more convenient. Let’s go ahead and install it:

yarn add nodemon --dev

And replace the node with nodemon in the start script:

./package.json

{
  "name": "graphql-tutorial",
  "version": "1.0.0",
  "main": "index.js",
  "license": "MIT",
  "scripts": {
    "start": "nodemon server.js"
  },
  "dependencies": {
    "express": "^4.16.4",
    "express-graphql": "^0.7.1",
    "graphql": "^14.0.2"
  },
  "devDependencies": {
    "nodemon": "^1.18.9"
  }
}

Now, whenever we edit and save a file Nodemon will conveniently restart for us and reflect the new changes.

If we also want to get advantage of the latest ES syntax we would like to install Babel.

 

Adding Babel 7

Starting with Babel7 the config setting changes quite a bit.

  1. Babels packages are now scoped and Babel has renamed it’s NPM packages. This means babel-cli for example has been renamed to @babel/cli .
  2. No messing around with presets anymore. You can just use @babel/preset-env now and optionally define your requirements in the config file.
  3. babel-node has been moved from the CLI to it’s own package: @babel/node

Let’s go ahead and install all necessary packages:

yarn add @babel/core @babel/cli @babel/node --dev

and tell nodemon to use Babel to transpile JS.

./package.json

{
  "name": "graphql-tutorial",
  "version": "1.0.0",
  "main": "index.js",
  "license": "MIT",
  "scripts": {
    "start": "nodemon --inspect=10111 --exec babel-node server.js"
  },
  "dependencies": {
    "express": "^4.16.4",
    "express-graphql": "^0.7.1",
    "graphql": "^14.0.2"
  },
  "devDependencies": {
    "nodemon": "^1.18.9"
  }
}

 

Before we could start using the new ES features we have to install a preset and tell Babel to use it by adding ./babelrc config file

yarn add @babel/preset-env --dev

add

./.babelrc

{
  "presets": [
    [
      "@babel/preset-env",
      {
        "useBuiltIns": "entry"
      }
    ]
  ]
}

 

give it a try …

./server.js

import express from 'express';
import graphqlHTTP from 'express-graphql';
import schema from './src/schema';


// Logger middleware
var logger = function(req, res, next) {
  console.log("GOT REQUEST >", req.ip);
  next(); // Passing the request to the next handler in the stack.
}

var app = express();
app.use(logger);
app.use('/graphql', graphqlHTTP({
  schema: schema,
  graphiql: true,
}));
app.listen(4000);
console.log('Running a GraphQL API server at localhost:4000/graphql');

 

Using GraphQLSchema to construct the schema programmatically.

For most of the cases defining a fixed schema when the application starts, by adding Query and Mutation types solely using schema language is good enough. But sometimes we might need to define a dynamic schema and we can achieve this by creating a new JS objects.

Construct dynamic schema for ‘User Greeting’ example.

Let’s get again to the ‘Greetings user’ example, because of it’s simplicity and define a Query with field named greetingUser which will accept userName and bornMonth parameters, first of type string and the second of type int and return userType.

And the userType will return greetingOne  which will simply say “Hello [userName]” and greetingTwo  that will let the user know how many months left till their next birthday. Both of type string.

./src/server.js

var express = require('express');
var graphqlHTTP = require('express-graphql');
var { buildSchema } = require('graphql');
var dogs = require('./src/models/mock_data/dogs.js');
const graphql = require('graphql');

// Define the User type
var userType = new graphql.GraphQLObjectType({
  name: 'UserType',
  fields: {
    greetingOne: { type: graphql.GraphQLString },
    greetingTwo: { type: graphql.GraphQLString },
  }
});

// Define the Query type
var queryType = new graphql.GraphQLObjectType({
  name: 'Query',
  fields: {
    greetingUser: {
      type: userType,
      args: {
        userName: { type: graphql.GraphQLString },
        bornMonth: { type: graphql.GraphQLInt }
      },
      resolve: function (_, {userName, bornMonth}) {
        var date = new Date();
        var daysLeft = bornMonth - (date.getMonth() + 1);
        daysLeft = daysLeft < 0 ? daysLeft + 12 : daysLeft;
        return {
          greetingOne: `Hello ${userName}`,
          greetingTwo: `Your birthday is comming in ${daysLeft} month(s)`
        };
      }
    }
  }
});

var schema = new graphql.GraphQLSchema({query: queryType});


// Logger middleware
var logger = function(req, res, next) {
  console.log("GOT REQUEST >", req.ip);
  next(); // Passing the request to the next handler in the stack.
}

var app = express();
app.use(logger);
app.use('/graphql', graphqlHTTP({
  schema: schema,
  graphiql: true,
}));
app.listen(4000);
console.log('Running a GraphQL API server at localhost:4000/graphql');

what we just did:
– we defined the user type which is pretty self explanatory (Lines 8-14)
– then we created the query type, that has these parameters:
type which is the return type, in this case userType
args is the input parameter types.
resolve is the resolver function.

Transform Dogs catalog to use dynamic schema.

Creating the dog type

var dogType = new graphql.GraphQLObjectType({
  name: 'dogType',
  fields: {
    id: { type: graphql.GraphQLString },
    breed: { type: graphql.GraphQLString },
    displayImage: { type: graphql.GraphQLString },
  }
});

Creating the query type

// Define the Query type
var queryType = new graphql.GraphQLObjectType({
  name: 'Query',
  fields: {
    getDogByBreed: {
      type: dogType,
      args: {
        breed: { type: graphql.GraphQLString }
      },
      resolve: function (_, {breed}) {

        var result = dogs.find(function(dog){
          return breed == dog.breed;
        });
        return result;
      }
    }
  }
});

Creating a mutation type

...
    addDogBreed: {
      type: graphql.GraphQLString,
      args: {
        id: { type: graphql.GraphQLString },
        breed: { type: graphql.GraphQLString },
        displayImage: { type: graphql.GraphQLString }
      },
      resolve: function (_, {id, breed, displayImage}) {
        dogs.push({
          id: id,
          breed: breed,
          displayImage: displayImage
        });
        return "OK!";
      }
    } 
...

Adding the query schema

...
var schema = new graphql.GraphQLSchema({query: queryType});

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

Putting it all together

var express = require('express');
var graphqlHTTP = require('express-graphql');
var { buildSchema } = require('graphql');
var dogs = require('./src/models/mock_data/dogs.js');
const graphql = require('graphql');

// Define the dogs type
var dogType = new graphql.GraphQLObjectType({
  name: 'dogType',
  fields: {
    id: { type: graphql.GraphQLString },
    breed: { type: graphql.GraphQLString },
    displayImage: { type: graphql.GraphQLString },
  }
});

// Define the Query type
var queryType = new graphql.GraphQLObjectType({
  name: 'Query',
  fields: {
    getDogByBreed: {
      type: dogType,
      args: {
        breed: { type: graphql.GraphQLString }
      },
      resolve: function (_, {breed}) {

        var result = dogs.find(function(dog){
          return breed == dog.breed;
        });
        return result;
      }
    },
    addDogBreed: {
      type: graphql.GraphQLString,
      args: {
        id: { type: graphql.GraphQLString },
        breed: { type: graphql.GraphQLString },
        displayImage: { type: graphql.GraphQLString }
      },
      resolve: function (_, {id, breed, displayImage}) {
        dogs.push({
          id: id,
          breed: breed,
          displayImage: displayImage
        });
        return "OK!";
      }
    }    
  }
});


var schema = new graphql.GraphQLSchema({query: queryType});


// Logger middleware
var logger = function(req, res, next) {
  console.log("GOT REQUEST >", req.ip);
  next(); // Passing the request to the next handler in the stack.
}

var app = express();
app.use(logger);
app.use('/graphql', graphqlHTTP({
  schema: schema,
  graphiql: true,
}));
app.listen(4000);
console.log('Running a GraphQL API server at localhost:4000/graphql');

Running GraphQL with Express sever

Create the project with all dependencies.

Create new node project -y will answer yes to all questions during setting up the project to save us some time.

yarn init -y

add the libraries:

yarn add graphql express express-graphql

what we just did:
– created new npm project (-y answering yes on all questions)
– installed graphQL (–save locally in project’s node_modules folder)
– installed Expless server with Express-graphQL module locally

Create server.js

Create ./server.js in the root folder with the following contents:

var express = require('express');
var { graphqlHTTP } = require('express-graphql');
var { buildSchema } = require('graphql');

// Construct a schema, using GraphQL schema language
var schema = buildSchema(`
  type Query {
    hello: String
  }
`);
// The root provides a resolver function for each API endpoint

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 localhost:4000/graphql');

what we just did:
– we created a schema (lines 6-10)
– created a resolver function for our schema (lines 13-17)
– created Express http server (line 19)
– tell Express http server to use graphQL library (lines 20-24)

Now everything is almost ready. Let’s create a start script and run our server.

Create the start script.

Open ./package.json and add the highlighted lines:

{
  "name": "graphql-test",
  "version": "1.0.0",
  "main": "index.js",
  "license": "MIT",
  "scripts": {
    "start": "node server.js"
  },  
  "dependencies": {
    "express": "^4.16.4",
    "express-graphql": "^0.7.1",
    "graphql": "^14.0.2"
  }
}

Run GraphQL server.

Open terminal, navigate to the root folder and execute the start script:

yarn start

Since we configured graphqlHTTP with graphiql: true (line 23) we can access GraphQL UI tool and execute queries.
Navigate the browser to http://localhost:4000/graphql and run { hello } and the result should look like the one in the image below:

Congratulations! You just ran your first GraphQL server!