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 {ASSIGNEDUNASSIGNEDIN_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 = falsestate: TaskStateEnumtaskPriority: Int = 1progress: Float = 0dueDate: DateTime}"""CreateTaskPayload type definition"""type CreateTaskPayload {task: Task!}"""An ISO-8601 encoded UTC date string."""scalar DateTimetype Mutation {createTask(input: CreateTaskInput!): CreateTaskPayload}type Query {tasks: [Task]!}type Task {id: ID!name: String!completed: Boolean!state: TaskStateEnum!progress: Float!taskPriority: Int!dueDate: DateTimecreatedAt: DateTime!updatedAt: DateTime}enum TaskStateEnum {ASSIGNEDUNASSIGNEDIN_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 {idnamecompletedstateprogresstaskPriority}}
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 {idnamecompletedstateprogresstaskPriority}}}
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 {idnamecompletedstateprogresstaskPriority}}}
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 {idname}}}
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.
Did you like this post? The repository with the examples and project set-up can be cloned from this branch. Feel free to send any questions about the topic to david@atheros.ai.