Constructing Types with the GraphQL Package

John Au-Yeung - Jan 25 '21 - - Dev Community

Check out my books on Amazon at https://www.amazon.com/John-Au-Yeung/e/B08FT5NT62

Subscribe to my email list now at http://jauyeung.net/subscribe/

We can create a simple GraphQL server with Express. To do this, we need the express-graphql and graphql packages.

In this article, we’ll look at how to add types that we can use to build a schema with the graphql package.

Constructing Types

We can construct a schema programmatically with the GraphQLSchema constructor that comes with the graphql package.

Instead of defining Query and Mutation types using the schema language, we can create them as separate object types.

For example, we can write the following to create a type with the graphql.GraphQLObjectType constructor to create an object type programmatically:

const express = require('express');
const graphqlHTTP = require('express-graphql');
const graphql = require('graphql');
const userType = new graphql.GraphQLObjectType({
  name: 'User',
  fields: {
    id: { type: graphql.GraphQLString },
    name: { type: graphql.GraphQLString },
  }
});

let users = {
  '1': {
    id: '1',
    name: 'Jane'
  }
}

const queryType = new graphql.GraphQLObjectType({
  name: 'Query',
  fields: {
    user: {
      type: userType,
      args: {
        id: { type: graphql.GraphQLString }
      },
      resolve: (_, { id }) => {
        return users[id];
      }
    }
  }
});

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

const app = express();
app.use('/graphql', graphqlHTTP({
  schema: schema,
  rootValue: root,
  graphiql: true,
}));
app.listen(3000, () => console.log('server started'));
Enter fullscreen mode Exit fullscreen mode

In the code above, we created the userType GraphQL data type by writing:

const userType = new graphql.GraphQLObjectType({
  name: 'User',
  fields: {
    id: { type: graphql.GraphQLString },
    name: { type: graphql.GraphQLString },
  }
});
Enter fullscreen mode Exit fullscreen mode

The name field defines the name of our type and the fields object has the fields that we include with the type. We defined id and name both to have type String .

Then we define our Query type with:

const queryType = new graphql.GraphQLObjectType({
  name: 'Query',
  fields: {
    user: {
      type: userType,
      args: {
        id: { type: graphql.GraphQLString }
      },
      resolve: (_, { id }) => {
        return users[id];
      }
    }
  }
});
Enter fullscreen mode Exit fullscreen mode

In the code above, we defined the name of the type to be Query . The fields we include is the user field, which is of type User that we defined above.

Also, we specified that we have the String id as the argument with the args property.

Finally, we have a resolve property with the resolver to return what we want to return.

In this case, we want to return the User from the users object given the id passed into the argument.

Then when we make the following query:

{
  user(id: "1"){
    id
    name
  }
}
Enter fullscreen mode Exit fullscreen mode

We get back:

{
  "data": {
    "user": {
      "id": "1",
      "name": "Jane"
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

since we have the following in the users object:

let users = {
  '1': {
    id: '1',
    name: 'Jane'
  }
}
Enter fullscreen mode Exit fullscreen mode

We can do the same with mutations.

This is particularly useful is we want to create a GraphQL schema automatically from something else like a database schema. We may have a common format for something like creating and updating database records.

It’s also useful for implementing features like union types that don’t map to ES6 constructs.

GraphQLUnionType

We can create union types with the GraphQLUnionType constructor.

To create a union type and use it in our app, we can use the GraphQLUnionType constructor as follows:

const express = require('express');
const graphqlHTTP = require('express-graphql');
const graphql = require('graphql');
class Dog {
  constructor(id, name) {
    this.id = id;
    this.name = name;
  }
};

class Cat {
  constructor(id, name, age) {
    this.id = id;
    this.name = name;
    this.age = age;
  }
};

const DogType = new graphql.GraphQLObjectType({
  name: 'Dog',
  fields: {
    id: { type: graphql.GraphQLString },
    name: { type: graphql.GraphQLString },
  }
});

const CatType = new graphql.GraphQLObjectType({
  name: 'Cat',
  fields: {
    id: { type: graphql.GraphQLString },
    name: { type: graphql.GraphQLString },
    age: { type: graphql.GraphQLInt },
  }
});

const PetType = new graphql.GraphQLUnionType({
  name: 'Pet',
  types: [DogType, CatType],
  resolveType(value) {
    if (value instanceof Dog) {
      return DogType;
    }
    if (value instanceof Cat) {
      return CatType;
    }
  }
});

let pets = {
  '1': new Dog('1', 'Jane'),
  '2': new Cat('1', 'Jane', 11),
}

const queryType = new graphql.GraphQLObjectType({
  name: 'Query',
  fields: {
    pet: {
      type: PetType,
      args: {
        id: { type: graphql.GraphQLString }
      },
      resolve: (_, { id }) => {
        return pets[id];
      }
    }
  }
});

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

const app = express();
app.use('/graphql', graphqlHTTP({
  schema: schema,
  rootValue: root,
  graphiql: true,
}));
app.listen(3000, () => console.log('server started'));
Enter fullscreen mode Exit fullscreen mode

In the code above, we created the Dog and Cat classes to serve as models for our data.

Then we create the GraphQL Dog and Cat types as follows:

const DogType = new graphql.GraphQLObjectType({
  name: 'Dog',
  fields: {
    id: { type: graphql.GraphQLString },
    name: { type: graphql.GraphQLString },
  }
});
const CatType = new graphql.GraphQLObjectType({
  name: 'Cat',
  fields: {
    id: { type: graphql.GraphQLString },
    name: { type: graphql.GraphQLString },
    age: { type: graphql.GraphQLInt },
  }
});
Enter fullscreen mode Exit fullscreen mode

We defined the DogType and CatType constants to define the Dog and Cat object types.

Dog has id and name fields and Cat has id , name , and age fields.

Then we defined the Pet union type, which is a union of Dog and Cat as follows:

const PetType = new graphql.GraphQLUnionType({
  name: 'Pet',
  types: [DogType, CatType],
  resolveType(value) {
    if (value instanceof Dog) {
      return DogType;
    }
    if (value instanceof Cat) {
      return CatType;
    }
  }
});
Enter fullscreen mode Exit fullscreen mode

Note that we have an array of types and a resolveType method instead of the resolve method.

Then finally, we create our query type so that we can return a response to the user as follows:

const queryType = new graphql.GraphQLObjectType({
  name: 'Query',
  fields: {
    pet: {
      type: PetType,
      args: {
        id: { type: graphql.GraphQLString }
      },
      resolve: (_, { id }) => {
        return pets[id];
      }
    }
  }
});
Enter fullscreen mode Exit fullscreen mode

The resolve function gets the pets entry by id and returns it, and we specified that the type we return is the PetType .

Once we did that, we can make our query using inline fragments as follows:

{
  pet(id: "1"){
    __typename,
    ...on Dog {
      id
      name
    }
    ...on Cat {
      id
      name
      age
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

In the query above, we discriminated between the fields of Dog and Cat by using the ...on operator. __typename gets the type of the object returned.

With that query, we should get:

{
  "data": {
    "pet": {
      "__typename": "Dog",
      "id": "1",
      "name": "Jane"
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

since we have a Dog instance with key '1' in pets .

On the other hand, if we make a query for Pet with ID 2 as follows:

{
  pet(id: "2"){
    __typename,
    ...on Dog {
      id
      name
    }
    ...on Cat {
      id
      name
      age
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

We get:

{
  "data": {
    "pet": {
      "__typename": "Cat",
      "id": "1",
      "name": "Jane",
      "age": 11
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

since we have a Cat instance as the object with key '2' in pets .

Conclusion

We can create types with GraphQLObjectType constructor to create object types.

To create union types, we can use the GraphQLUnionType , then we have to resolve the type in the resolveType method by checking the type of the object and returning the right one.

We can query union types with inline fragments and check the type with __typename .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .