Charly Poly Senior Software Engineer at Double, former Tech Lead at Algolia. I'm passionate about web software engineering, from frontend development to working on APIs, mostly on TypeScript, GraphQL, and Ruby ecosystems.

What you need to know about GraphQL enums

6 min read 1727

What You Need to Know About GraphQL Enums

GraphQL is a language and set of technologies that make querying data easier for modern frontend applications. Named the technology developers are most interested in learning in the “2019 State of JavaScript” report, GraphQL is recognized as a scalable and production-ready tool that is used by many industry giants such as Github, Netflix, and Shopify, to name a few.

While most features of GraphQL are well-understood (e.g., directives, input types, resolvers), enum types are often underrated and their usefulness largely unknown. In this tutorial, we’ll shed light on why enums are important and explore the essential role this simple type plays in building robust GraphQL APIs.

GraphQL schemas

GraphQL APIs rely on GraphQL schemas to expose data. GraphQL provides it owns scalar types (Int, Float, String, Boolean, and ID) and lets you create your own data types using the following keywords.

  • type to define complex object types used by queries (read data)
  • input to define input types used as arguments by mutations (create, update, delete data)

We’ll base all our examples on the following schema describing a user management API.

# A User (profile) is linked to some Accounts that carries all authentication information
type Account {
  id: ID! # "!" indicate a non-null (mandatory) field
  authType: String! # "google-auth", "github-auth", "outlook-auth"
  email: String!
  token: String
  archived: Boolean!
  disabled: Boolean!
  verified: Boolean!
}

type User {
  id: ID!
  accounts: [Account!]! # (a non-null array of non-null Account values)
  givenName: String!
}

# a single query is exposed to list all users and filter by status and authType
type Query {
  users(first: Int!, from: ID, status: String, authType: String): [User!]!
}

# we define an "input" type for our updateAccount mutation
input UpdateAccountInput {
  id: ID!
  state: String! # "verified", "disabled", "archived"
}

type Mutation {
  updateAccount(input: UpdateAccountInput!): Account!
}

Our GraphQL API allows us to list all users with some optional filters.

  • status, of String type, allows us to filter the multiple boolean fields of User using a simple String value: “verified”, “disabled”, “archived”
  • authType is based on Account.authType

The API uses the Account.authType value (“google-auth”, “github-auth”, “outlook-auth”) internally to leverage the proper provider to authenticate but also to get user profile information. “google-auth” leads to the appropriate logic under the GoogleProfileProvide namespace.

We can get the first 10 active users to use Google by doing the following.

   user(first: 10, authType: "google-auth", status: "verified") {
      id
      # ...
   }

Our API also exposes an updateAccount mutation, which allows us to update an account status during its lifecycle.

GraphQL enums Account authType Values

This schema is functional as-is. However, we can make it way more robust by leveraging GraphQL enums.

Why use enums?

Although when using String, Account.authType, and UpdateAccountInput.state we can expect to receive specific values, this can introduce issues at multiple levels.

Strings are case-sensitive and break GraphQL validations

Below is an example of how you might use the users query.



query {
  users(first: 10, status: "Verified") {
   id
  }
}

GraphQL-wise, on the client side (e.g., Apollo), this query is entirely valid. However, can you spot the typo?

The status value “Verified”, should be written as “verified”.

Having a free-form String type here might trigger unexpected 500s, silent errors, or, worse, security vulnerabilities.

To prevent this, we’ll have to add some validation logic to our users query resolver.

interface UsersArgs {
  first: number,
  from?: string,
  status?: String,
  authType?: string,
}

const STATUSES = ['', '', ]

export const resolvers = {
  Query: {
    users: (_record: never, args: UsersArgs, _context: never) {
      if (args?.status &&  !STATUSES.includes(args.status)) {
        // throw error
      }
      // ...
    }
  }
}

This is quite counterproductive since the principal value of GraphQL type-based schemas is to provide validation out of the box on operations and data. Using enums will limit the status value to a given set of defined values and avoid duplicate validations logic.

Exposing internal values

We saw that Account.authType relys on a String type, which leads to the following kind of users query.

query {
  users(first: 10, authType: "google-auth") {
   id
  }
}

Such a pattern is not optimal because exposing internal values goes against the principle that APIs should expose a simple, decoupled interface with data.


More great articles from LogRocket:


Again, we can improve our users query resolver to allows public-friendly values for filtering.

interface UsersArgs {
  first: number,
  from?: string,
  status?: String,
  authType?: string,
}

const AUTH_TYPES_FILTER_MAP = {
   'GOOGLE': 'google-auth',
   'GITHUB': 'github-auth,
   'OUTLOOK': 'outlook-auth',
}

export const resolvers = {
  Query: {
    users: (_record: never, args: UsersArgs, _context: never) {
      // does both mapping and validation (invalid value will be ignored)
      const authType = AUTH_TYPES_FILTER_MAP[args.authType]
      // ...
    }
  }
}

This would allow the following query.

query {
  users(first: 10, authType: "GOOGLE") {
   id
  }
}

However, here again, we have to build a feature already provided by GraphQL via enums.

In the next section, we’ll walk through how to map enums to internal values by migrating our schema to enums.

Discoverability issue (TypeScript, GraphiQL/Explorer)

The last downside of relying on the String type for filter- or choice-based values is discoverability.

If you have an UpdateAccountInput.status that relies on the String type, GraphQL won’t be able to inspect the possible values of UpdateAccountInput.status during the introspection query since only the resolvers know about them.

GraphQL introspection is one of the core features of GraphQL that allows clients to dynamically inspect the schema definition by doing a GraphQL query (inception).

Here’s an example listing all the queries defined on our schema:

query {
  __schema {
    queryType {
      fields {
        name
        args {
          name
          type {
            name
            description
          }
        }
        description
        deprecationReason
      }
    }
  }
}

{
  "data": {
    "__schema": {
      "queryType": {
        "fields": [
          {
            "args": [
              {
                "name": "authType",
                "type": {
                  "name": "String",
                  "description": null
                }
              },
              {
                "name": "first",
                "type": {
                  "name": null,
                  "description": null
                }
              },
              {
                "name": "from",
                "type": {
                  "name": "ID",
                  "description": null
                }
              },
              {
                "name": "status",
                "type": {
                  "name": "String",
                  "description": null
                }
              }
            ],
            "deprecationReason": null,
            "name": "users",
            "description": "perform the action: \"users\""
          }
        ]
      }
    }
  }
}

Tools like GraphiQL use introspection to provide autocomplete functionality. GraphQL Code Generator also uses it to generate TypeScript types.

String types do not allow us to inspect the possible values.

String Types Discoverability Issue

GraphQL enums are introspectable, allowing the app to autocomplete possible values and generate corresponding TypeScript types.

Migrating to enums

Relying on String may lead to security issues, unnecessary validation logic on the resolvers side, and a poor developer experience.

Let’s make our GraphQL Schema more robust by migrating to enums.

How to use enums

GraphQL enums can be defined using the enum keyword.

enum AuthType {
   GOOGLE
   GITHUB
   OUTLOOK
}

According to the official specification, you should name enums values using the all-caps convention. Enum values don’t need any common prefix since they are namespaced by the enum’s name.

Updating the schema

Let’s take a look at our new enums-based schema:

enum AuthType {
   GOOGLE
   GITHUB
   OUTLOOK
}

# A User (profile) is linked to some Accounts that carries all authentication information
type Account {
  id: ID! # "!" indicate a non-null (mandatory) field
  authType: AuthType!
  email: String!
  token: String
  archived: Boolean!
  disabled: Boolean!
  verified: Boolean!
}

type User {
  id: ID!
  accounts: [Account!]! # (a non-null array of non-null Account values)
  givenName: String!
}

# a single query is exposed to list all users and filter by status and authType
type Query {
  users(first: Int!, from: ID, status: String, authType: String): [User!]!
}

enum AccountState {
   VERIFIED,
   DISABLED,
   ARCHIVED
}

# we define an "input" type for our updateAccount mutation
input UpdateAccountInput {
  id: ID!
  state: AccountState!
}

type Mutation {
  updateAccount(input: UpdateAccountInput!): Account!
}

Enum internal values

Enum value resolution (at resolver and client side) is an implementation detail, meaning that each library chooses how to resolve a given enum value to a language-specific value.

With Apollo Server, each enum value will resolve to its string equivalent.

  • AuthType.GOOGLE resolves to “GOOGLE”
  • AuthType.GITHUB resolves to “GITHUB”
  • AuthType.OUTLOOK resolves to “OUTLOOK”

However, our API expected manipulated internal values (“google-auth”, “github-auth”, “outlook-auth”) to operate appropriately.

Per the official documentation, Apollo Server enables you to provide enum resolvers, as shown below with our updated users query resolver.

JavaScript version:

export const resolvers = {
  AuthType: {
   GOOGLE: 'google-auth',
   GITHUB: 'github-auth',
   OUTLOOK: 'outlook-auth',
  },
  Query: {
    users: (_record, args, _context) {
      // args.authType will always be 'google-auth' or  'github-auth' or 'outlook-auth'
      // ...
    }
  }
}

TypeScript version:

enum AuthType {
 GOOGLE =  'google-auth',
 GITHUB =  'github-auth',
 OUTLOOK = 'outlook-auth',
}

interface UsersArgs {
  first: number,
  from?: string,
  status?: String,
  authType?: AuthType,
}

export const resolvers = {
  AuthType,
  Query: {
    users: (_record: never, args: UsersArgs, _context: never) {
      // args.authType will always be 'google-auth' or  'github-auth' or 'outlook-auth'
      // ...
    }
  }
}

Note that with TypeScript, an enum type can be directly passed as a resolver.

Apollo Server will resolve enum values to their proper internal values (resolvers.AuthType) for both input and output data (Mutation arguments and Query returned data).

Now that our schema relies on enums, let’s see how it improves our queries and mutations usage.

Querying with enums

We can now query our users and filter using our AuthType enum.

query {
  users (first: 10, authType: GOOGLE) {
    id
  }
}

Our query is now safe, typoless, and fully discoverable.

GraphQL Enums TypeScript Types Generation

But the enum value as String is not valid. The field using an enum type requires an enum reference, so passing the enum value isn’t considered valid.

String Types Discoverability Issue Enum Error Prompt

Bonus: TypeScript generation

GraphQL enums are introspectable, as shown below.

Introspectable GraphQL enums

Introspectable enums mean that tools like GraphQL Code Generator will generate the corresponding TypeScript enum type for your frontend application.

With our schema, GraphQL code generator will be able to create the corresponding TypeScript enum:

export enum AuthType {
 GOOGLE =  'google-auth',
 GITHUB =  'github-auth',
 OUTLOOK = 'outlook-auth',
}

This will come in handy for later use (useQuery(), useMutation()):

const ActiveUsersList = () => {
  const { loading, data } = useQuery(
    usersQueryDocument,
    {
      variables: {
        first: 10,
        authType: AuthType.GOOGLE,
      }
    })

  // ...
}

Relying on both GraphQL enums and TypeScript-generated enums makes for a robust and powerful developer experience.

Summary

Even on small GraphQL APIs, such as the User Management API, enums bring a lot of consistency and make your schema more robust.

In this tutorial, we explored how GraphQL enums can help:

  • Provide a more robust and discoverable API — Every list-based or filtering field of your schema should leverage enums to avoid security, typo-related, and discoverability issues. Using enums a great example of embracing the main power of GraphQL: semantic operations and on-flight data validation
  • Expose a simple interface — The main goal of an API is to provide a simplified interface over data or complex systems. This is even more true with GraphQL, which brought natural, JSON-like APIs to the frontend world. GraphQL enums are an excellent approach to abstracting internal values to simple choices when it comes to filtering
  • Maintain slim resolvers — When building a GraphQL API, it’s essential not to reimplement the features provided by GraphQL. If the main features of GraphQL are operation validation (e.g., operation asked, the structure of arguments) and data validation (for both input and output), the resolvers should be slim by only focusing on the business logic (e.g., data manipulation, extra complex validation)

 

Monitor failed and slow GraphQL requests in production

While GraphQL has some features for debugging requests and responses, making sure GraphQL reliably serves resources to your production app is where things get tougher. If you’re interested in ensuring network requests to the backend or third party services are successful, try LogRocket.https://logrocket.com/signup/

LogRocket is like a DVR for web and mobile apps, recording literally everything that happens on your site. Instead of guessing why problems happen, you can aggregate and report on problematic GraphQL requests to quickly understand the root cause. In addition, you can track Apollo client state and inspect GraphQL queries' key-value pairs.

LogRocket instruments your app to record baseline performance timings such as page load time, time to first byte, slow network requests, and also logs Redux, NgRx, and Vuex actions/state. .
Charly Poly Senior Software Engineer at Double, former Tech Lead at Algolia. I'm passionate about web software engineering, from frontend development to working on APIs, mostly on TypeScript, GraphQL, and Ruby ecosystems.

4 Replies to “What you need to know about GraphQL enums”

  1. Great write-up, thank you!

    I have a question though: It isn’t clear to me how the client side can know the internal values for the enum. Since Apollo has devs define those internal values in a resolver, they don’t show up in the server schema itself, and therefore when you tell codegen to run for the client, it can’t know about the internal values. Am I missing some helpful setting or something? Do you have a complete version of your example in a repo?

  2. Hi Micah,

    Thank you for you comment!

    Although I’m not an expert of Apollo Server inner working, I do believe that Apollo Server is using enums “internal values” passed as resolvers to build the schema and pass them as default value to graphql-js (https://github.com/graphql/graphql-js/issues/1604)

    Since those enum internal values are passed to the Schema definition “magically” by Apollo Server, it explains how you end-up having them in the introspection query results used by tools like GraphQL code generator.

    Please let me know if it answers your question!

Leave a Reply