Nexus: TypeScript Use enum outside of resolvers

Created on 27 Nov 2019  路  2Comments  路  Source: graphql-nexus/nexus

Hi there,

I have declared an enum using nexus enumType function.

export const SubscriptionPlanEnum = enumType({ name: "SubscriptionPlanEnum", members: { SOLO: "soloID", MULTI: "multiID", } });

but then I would like sometimes to use that enum as a type hint for a typescript function :

static async Test(plan : SubscriptionPlanEnum)

but it says that it seems to be a value not a type.

What is the best way to use that enum? If not possible, what is the best workaround?

Thank you very much!

Most helpful comment

The enumType from nexus will build a GraphQLEnumType under the hood which is not equivalent to a TypeScript enum.

If you're wanting to define an enum once and you'd like to use it both in TypeScript and in your schema, you can use define the enum in TypeScript and pass it to enumType (it will know how to extract the correct members out of it).

That would look something like this:

// User.ts
export enum UserRoles {
  ADMIN = "admin",
  EDITOR = "editor",
  GUEST = "guest"
}
// Schema.ts
import { objectType, enumType } from "nexus";
import { UserRoles } from "./User";

// Pass the UserRoles enum straight into enumType
export const UserRoleEnumType = enumType({
  name: "UserRole",
  members: UserRoles
});

// You should be able to use it now just like this.
export const UserType = objectType({
  name: "User",
  definition(t) {
    t.id("id");
    t.field("role", {
      type: "UserRole",
      resolve: () => UserRoles.ADMIN
    });
  }
});

The generated schema will look as follows:

type User {
  id: ID!
  role: UserRole!
}

enum UserRole {
  ADMIN
  EDITOR
  GUEST
}

...and the corresponding type definitions will look like this:

export interface NexusGenEnums {
  UserRole: "admin" | "editor" | "guest"
}

This should be sufficient if you're only going to use the enum as a return type. If you're also going to accept it as input types and you want to have this work properly with TypeScript you'll need additional source in the typegenAutoConfig of your makeSchema call.

For example if you want to use your enum as input for a mutation like so:

function updateUserRole(role: UserRoles) {
  // update the users role...
}

export const UpdateUserRole = mutationField("updateUserRole ", {
  type: "User",
  args: {
    role: arg({ type: "UserRole", nullable: false })
  },
  resolve: (_, args) => {
    return updateUserRole(args.role);
  }
});

If you don't set up a proper source you'll notice that TypeScript will not let you go from "admin" | "editor" | "guest" to a UserRoles enum type. This is normal for TypeScript. If you want nexus to fix this for you, you can add a source config like so:

makeSchema({
  types: [UserType, UserRoleEnumType, UpdateUserRole],
  outputs: {
    schema: path.join(__dirname, "./schema.graphql"),
    typegen: path.join(__dirname, "./types..d.ts")
  },
  typegenAutoConfig: {
    sources: [
      {
        alias: "usr",
        source: path.join(__dirname, "./User.ts"),
        typeMatch: name =>
          // the default implementation doesn't include enum in the regex.
          new RegExp(`(?:interface|type|class|enum)\\s+(${name}s?)\\W`, "g")
      }
      }
    ]
  }
});

What will happen now is that nexus should find the UserRoles name match and import an alias into the type definitions like so:

import * as usr from "./User"

export interface NexusGenEnums {
  UserRole: usr.UserRoles
}

This will now also reflect in the types for the args argument in your resolver and the types should match.

As far as I know this is the most elegant way thus far to only define an enum once and use it in both your schema and code.

All 2 comments

The enumType from nexus will build a GraphQLEnumType under the hood which is not equivalent to a TypeScript enum.

If you're wanting to define an enum once and you'd like to use it both in TypeScript and in your schema, you can use define the enum in TypeScript and pass it to enumType (it will know how to extract the correct members out of it).

That would look something like this:

// User.ts
export enum UserRoles {
  ADMIN = "admin",
  EDITOR = "editor",
  GUEST = "guest"
}
// Schema.ts
import { objectType, enumType } from "nexus";
import { UserRoles } from "./User";

// Pass the UserRoles enum straight into enumType
export const UserRoleEnumType = enumType({
  name: "UserRole",
  members: UserRoles
});

// You should be able to use it now just like this.
export const UserType = objectType({
  name: "User",
  definition(t) {
    t.id("id");
    t.field("role", {
      type: "UserRole",
      resolve: () => UserRoles.ADMIN
    });
  }
});

The generated schema will look as follows:

type User {
  id: ID!
  role: UserRole!
}

enum UserRole {
  ADMIN
  EDITOR
  GUEST
}

...and the corresponding type definitions will look like this:

export interface NexusGenEnums {
  UserRole: "admin" | "editor" | "guest"
}

This should be sufficient if you're only going to use the enum as a return type. If you're also going to accept it as input types and you want to have this work properly with TypeScript you'll need additional source in the typegenAutoConfig of your makeSchema call.

For example if you want to use your enum as input for a mutation like so:

function updateUserRole(role: UserRoles) {
  // update the users role...
}

export const UpdateUserRole = mutationField("updateUserRole ", {
  type: "User",
  args: {
    role: arg({ type: "UserRole", nullable: false })
  },
  resolve: (_, args) => {
    return updateUserRole(args.role);
  }
});

If you don't set up a proper source you'll notice that TypeScript will not let you go from "admin" | "editor" | "guest" to a UserRoles enum type. This is normal for TypeScript. If you want nexus to fix this for you, you can add a source config like so:

makeSchema({
  types: [UserType, UserRoleEnumType, UpdateUserRole],
  outputs: {
    schema: path.join(__dirname, "./schema.graphql"),
    typegen: path.join(__dirname, "./types..d.ts")
  },
  typegenAutoConfig: {
    sources: [
      {
        alias: "usr",
        source: path.join(__dirname, "./User.ts"),
        typeMatch: name =>
          // the default implementation doesn't include enum in the regex.
          new RegExp(`(?:interface|type|class|enum)\\s+(${name}s?)\\W`, "g")
      }
      }
    ]
  }
});

What will happen now is that nexus should find the UserRoles name match and import an alias into the type definitions like so:

import * as usr from "./User"

export interface NexusGenEnums {
  UserRole: usr.UserRoles
}

This will now also reflect in the types for the args argument in your resolver and the types should match.

As far as I know this is the most elegant way thus far to only define an enum once and use it in both your schema and code.

You are awesome!! That couldn鈥檛 be more helpful! Thank you very much, I鈥檒l setup it right away. Cheers

Was this page helpful?
0 / 5 - 0 ratings

Related issues

MathiasKandelborg picture MathiasKandelborg  路  5Comments

ecwyne picture ecwyne  路  6Comments

jasonkuhrt picture jasonkuhrt  路  5Comments

jasonkuhrt picture jasonkuhrt  路  3Comments

hatchli picture hatchli  路  5Comments