Learning Logo
Mobile Menu
Copy article linkShare Article on TwitterShare article on LinkedInShare article on FacebookShare article on Pinterest

How to use GraphQL enum and its best practices

David Mráz
David Mráz@davidm_ai
Author's Linkedin accountAuthor's Instagram accountAuthor's Twitter accountAuthor's Twitter account
Development

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 git@github.com:atherosai/graphql-gateway-apollo-express.git

install dependencies with

npm i

and start the server in development with

npm run dev

You should be now able to access GraphQL Playground

In this part, we will go through the fields that use Enum fields in our previously defined schema. If you are new to GraphQL, it might also be helpful to check out our previous articles on built-in scalars as well as the one on input object type. 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 default TaskStateEnumType

The enum class allows us to map enum values to internal values represented by integers (or different strings etc.). 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 DateTime from '../custom-scalars/DateTime';
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),
defaultValue: false
},
state: {
type: new GraphQLNonNull(TaskStateEnumType),
},
progress: {
type: new GraphQLNonNull(GraphQLFloat),
},
taskPriority: {
type: new GraphQLNonNull(GraphQLInt),
},
dueDate: {
type: DateTime,
},
createdAt: {
type: new GraphQLNonNull(DateTime),
},
updatedAt: {
type: DateTime,
},
}),
});
export default TaskType

We will also use our TaskStateEnumType for validating input when creating task. The type called CreateTaskInput is defined as follows:

import {
GraphQLString,
GraphQLInputObjectType,
GraphQLNonNull,
GraphQLBoolean,
GraphQLInt,
GraphQLFloat,
} from 'graphql';
import TaskStateEnum from './TaskStateEnumType';
import DateTime from '../custom-scalars/DateTime';
const CreateTaskInputType = new GraphQLInputObjectType({
name: 'CreateTaskInput',
fields: () => ({
name: {
type: new GraphQLNonNull(GraphQLString),
},
completed: {
type: GraphQLBoolean,
defaultValue: false,
},
state: {
type: TaskStateEnum,
defaultValue: TaskStateEnum.getValue("ASSIGNED"),
},
taskPriority: {
type: GraphQLInt,
defaultValue: 1,
},
progress: {
type: GraphQLFloat,
defaultValue: 0,
},
dueDate: {
type: DateTime,
},
}),
});
export default CreateTaskInputType;

You can also see, that we defined defaultValue for state field with in-build function called getValue, which is available for all Enum objects. It is a good practice to define all enumerated fields at one place and then just reuse them through the whole codebase.

All possible values for the state field are now available through introspection and can be viewed in GraphQL Playground in the Docs tab. The Schema can be also written in SDL:

input CreateTaskInput {
name: String!
completed: Boolean = false
state: TaskStateEnum
taskPriority: Int = 1
progress: Float = 0
dueDate: DateTime
}
"""CreateTaskPayload type definition"""
type CreateTaskPayload {
task: Task!
}
"""An ISO-8601 encoded UTC date string."""
scalar DateTime
type Mutation {
createTask(input: CreateTaskInput!): CreateTaskPayload
}
type Query {
tasks: [Task]!
}
type Task {
id: ID!
name: String!
completed: Boolean!
state: TaskStateEnum!
progress: Float!
taskPriority: Int!
dueDate: DateTime
createdAt: DateTime!
updatedAt: DateTime
}
enum TaskStateEnum {
ASSIGNED
UNASSIGNED
IN_PROGRESS
}

Now let’s move on to result and input coercion for enums. If you do not know what that means, it might be useful to go through the article on scalars and its input and result coercion, where we explain these terms.

Result coercion for enums

If we receive a different value from the database, that is not defined in the enum type, an error is raised. For example, let’s change the value of the model Task state in the “in-memory” db (task-db.ts) to badstate.

let tasks = [
{
id: '7e68efd1',
name: 'Test task',
completed: 0.0,
createdAt: '2017-10-06T14:54:54+00:00',
updatedAt: '2017-10-06T14:54:54+00:00',
taskPriority: 1,
progress: 55.5,
state: "badstate",
},
];
export default tasks;

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 takes care 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 createTask {
createTask(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 common error when using Enums is the execution of this createTask mutation

mutation createTask {
createTask(input: {
name: "Next task",
state: "ASSIGNED"
}) {
task {
id
name
completed
state
progress
taskPriority
}
}
}

It is not always expected that we will get 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 GraphQL Playground is just to specify enumerated values without the quotation

mutation createTask {
createTask(input: {
name: "Next task",
state: ASSIGNED
}) {
task {
id
name
completed
state
progress
taskPriority
}
}
}

This is different, of course, when we specify mutations using variables, as the variable has to be written according to JSON syntax specification. When using variables the GraphQL document will look as follows:

mutation createTask($input: CreateTaskInput!) {
createTask(input: $input) {
task {
id
name
}
}
}

with this variables:

{
"input": {
"name": "Next task",
"state": "ASSIGNED"
}
}

Conclusion

In GraphQL, Enum type can be used as input and output type. Enums are the great way to add additional validation constraints. We can list the values with introspection query:

query introspectTaskStateEnumType {
__type(name: "TaskStateEnum") {
enumValues {
name
}
}
}

and reuse the Enumerated types across our whole code base.

Ready to take next step?

Unlock your potential and master the art of development and design by joining our Classes today.

Don't miss out on the opportunity to enhance your skills and create a bright future in the digital world like thousands of others.