Nov 7th 2017

How to use GraphQL enum type and its best practices

Author profileDavid Mráz

Introduction

We will continue with the same repository as in the previous article on GraphQL scalars. You can clone the GitHub repository using this command

git clone https://github.com/a7v8x/express-graphql-demo.git -b feature/3-graphql-scalars

In this part, we will extend the fields of Task object type defined in our schema. If you are new to GraphQL, it might also be helpful to check out our previous articles, especially the one oninput object type and setting up basic GraphQL server. We use them as prerequisites for this article in terms of understanding GraphQL, and we will also use parts of the code that we built in previous articles. In the following image we illustrate the hierarchical tree graph of the response for our queries and mutations

Enum Types

Enums are basically a special type we can use to enumerate all possible values in the field. By using Enums we are adding another kind of validation to existing GraphQL schema. The specified values of the Enum type are the only possible options that are accepted. Now let’s go right into the implementation.

Let’s consider that in our Task type we also define Enum for the task state. We design this enum type with the consideration that a given task can have one of three states:

  • ASSIGNED;
  • UNASSIGNED;
  • IN_PROGRESS;

In the GraphQL query language we can write it in the following form

enum TaskStateEnum {
  ASSIGNED
  UNASSIGNED
  IN_PROGRESS
}

It is common good practice to label enum values as capitalized values. In GraphQL language, these values are automatically mapped to the name of the value. However, when we rewrite the TaskStateEnum with graphql-js, we can also write the enum type in the following form:

import {
  GraphQLEnumType,
} from 'graphql';

const taskStateEnumType = new GraphQLEnumType({
  name: 'TaskStateEnum',
  values: {
    ASSIGNED: {
      value: 0,
    },
    UNASSIGNED: {
      value: 1,
    },
    IN_PROGRESS: {
      value: 2,
    },
  },
});

export {
  taskStateEnumType as default,
};

The enum class allows us to map enum values to internal values represented by integers (or different strings etc.). However, why we map the string values onto integer values in this example? By defining enumerated values to an integer leads you to design your schema in the most efficient way in terms of performance. In our case, we did not use any real database and did not send it to some more complex backend infrastructure. However, in production you will often use some monolithic or microservice architecture. It is much more efficient to send these enum values using integers, as the size is much smaller and can be transferred over the network more quickly. Also, it is more efficient to store integer values in the database.

Including our Enum in the database

Now we can implement our defined state in our Task definition:

import {
  GraphQLString,
  GraphQLID,
  GraphQLObjectType,
  GraphQLNonNull,
  GraphQLInt,
  GraphQLFloat,
  GraphQLBoolean,
} from 'graphql';

import TaskStateEnumType from './taskStateEnumType';

const taskType = new GraphQLObjectType({
  name: 'Task',
  fields: () => ({
    id: {
      type: new GraphQLNonNull(GraphQLID),
    },
    name: {
      type: new GraphQLNonNull(GraphQLString),
    },
    completed: {
      type: new GraphQLNonNull(GraphQLBoolean),
    },
    state: {
      type: new GraphQLNonNull(TaskStateEnumType),
    },
    progress: {
      type: new GraphQLNonNull(GraphQLFloat),
    },
    taskPriority: {
      type: new GraphQLNonNull(GraphQLInt),
    },
  }),
});

export {
  taskType as default,
};

All possible values for the state field are now available through introspection in GraphiQL

Now let’s move on to result and input coercion. If you do not know what that means, it might be usefull to go through the article on scalars and its input and result coercion.

Result coercion for enums

If we receive a different value from the database, that is not defined in enum type, an error is raised. For example, let’s change the value of the model Task state in the “in-memory” db (taskDb.js file) to badstate. And then execute the following query for retrieving tasks from the server

query getTasks {
  tasks {
    id
    name
    completed
    state
    progress
    taskPriority
  }
}

The GraphQL server will raise the following error:

Expected a value of type ”TaskStateEnumType” but received: badstate

The important thing to recognize is also the fact that if we change the state from integer value 1 (UNASSIGNED) to the string value UNASSIGNED the GraphQL will also raises the same type of error:

Expected a value of type ”TaskStateEnumType” but received: UNASSIGNED

GraphQL server cares about internal value mapping when dealing with result coercion, therefore will raise the error when no internal enum values matches the received data. If the data are matched to enumerated values, these data values are then mapped according to enum specification, e.g. 2 will be mapped to IN_PROGRESS. However, when we use float 1.0 as a value for the state field of the task in the “in-memory” db. The value is then transformed to the integer 1 and GraphQL does not raise an error as the data are available in the TaskStateEnum specification.

Input coercion for enums

When we deal with input coercion for enums, we have to take into account additional validation for enumerated values. The GraphQL server will check if the values for the enum field matches defined values in the schema. Therefore if we execute the following mutation for adding task with state argument badstate

mutation addTask {
  addTask(input: {name: "task with bad state", state: badstate}) {
    id
  }
}

the GraphQL server would raise the following error.

Argument "input" has invalid value {name: "task with bad state", state: badState}.
In field "state": Expected type "TaskStateEnumType", found badState.

The offen error in graphiql is the execution of this addTask mutation

mutation addTask {
  addTask(input: {name: "Next task", state: "ASSIGNED"}) {
    id
  }
}

It is not allways predictable that it will raise the following error

Argument "input" has invalid value {name: "Next task", state: "ASSIGNED"}.
In field "state": Expected type "TaskStateEnumType", found "ASSIGNED".

The correct way to pass enum inline arguments in GraphiQL is just to specify enumerated values without the quotation

mutation addTask {
  addTask(input: {name: "Next task", state: ASSIGNED}) {
    id
  }
}

This is different, of course, when we specify mutations using variables, as the variable has to be written according to JSON syntax specification. In that case, we have to use strings. You can check out the GraphQL specification for more information on this topic.

Did you like this post? The repository with the examples and project setup can be cloned from this branch . Feel free to send any questions about the topic to david@atheros.ai.

Join thousands of others and be occasionally notified about new articles and our courses

* Signing up for Atheros Intelligence newsletter indicates you agree with Terms and Conditions and Privacy Policy including our Cookie Policy.