Plugins

Add GraphQL plugin

Bring types and whole schemas from an existing graphql-js service into a Pothos builder while you migrate to code-first.

The add-graphql plugin brings types from an existing executable GraphQL schema into a Pothos builder. Point it at a GraphQLObjectType, a handful of types, or a whole GraphQLSchema and Pothos re-registers them as its own, so you can migrate a service built on nexus, graphql-tools, or plain graphql-js one piece at a time while new fields are written in Pothos.

There are two entry points. The add builder option pulls types in bulk when you construct the builder; the addGraphQL methods pull them one at a time and hand back a ref you can customize and use in field definitions.

Install

npm install --save @pothos/plugin-add-graphql
import AddGraphQLPlugin from '@pothos/plugin-add-graphql';

const builder = new SchemaBuilder({
  plugins: [AddGraphQLPlugin],
});

Import an existing schema

Pass add when constructing the builder to fold existing types into the schema at build time. add.schema imports every type in a GraphQLSchema; add.types imports a specific list. Either way, any type reachable through a field, interface, or union member is imported recursively, so you don't have to enumerate dependencies.

const legacySchema = new GraphQLSchema({ types: [LegacyTeam] });

// Registering Team on the Objects generic lets you reference it by name.
const builder = new SchemaBuilder<{
  Objects: { Team: ITeam };
}>({
  plugins: [AddGraphQLPlugin],
  add: {
    // Import every type in the schema; dependencies come along recursively.
    schema: legacySchema,
  },
});

builder.queryType({
  fields: (t) => ({
    teams: t.field({
      type: ['Team'],
      resolve: () => [...Teams.values()],
    }),
  }),
});

add.types accepts an array of GraphQLNamedType (objects, interfaces, unions, enums, scalars, and input objects all qualify), or a name-keyed record of the same:

const builder = new SchemaBuilder({
  plugins: [AddGraphQLPlugin],
  add: {
    types: [legacySchema.getType('Team')!, legacySchema.getType('Division')!],
  },
});

Imported types have to be referenced to be useful. Register them on the builder's generic SchemaTypes and you can name them as strings anywhere Pothos expects a type, as teams does with ['Team'] above. This shortcut covers object, interface, and scalar types only; for unions, enums, and inputs, use the addGraphQL methods below to get a ref instead.

A type is imported only if no type of the same name is already registered on the builder before the schema is built. A name you define yourself always wins, so the plugin never clobbers your own types, but it also silently skips an imported type whose name you've already taken.

Importing a schema that has its own Query, Mutation, or Subscription merges those root fields by calling builder.queryType (or mutationType/subscriptionType) for you. Add your own root fields with builder.queryFields so they merge with the imported ones. Defining your own builder.queryType instead does not error; the plugin silently skips the imported root type, dropping its fields.

Refs for individual types

The addGraphQL methods import one type and return a ref (an ObjectRef, InterfaceRef, and so on) that behaves exactly like a ref you built from scratch. Use it as a field's type, and customize the imported type on the way in. Passing a generic Shape is recommended: it types the ref's backing model so resolvers stay checked.

// addGraphQLInput returns an InputObjectRef you can hang on any arg.
const PlayerFilter = builder.addGraphQLInput<{ position?: 'HANDLER' | 'CUTTER' }>(LegacyPlayerFilter);

// addGraphQLObject returns an ObjectRef. Customize fields as you import:
// null drops a field, and new entries are merged in alongside the rest.
const Player = builder.addGraphQLObject<IPlayer>(LegacyPlayer, {
  fields: (t) => ({
    fullName: null,
    displayName: t.exposeString('fullName'),
  }),
});

builder.queryType({
  fields: (t) => ({
    players: t.field({
      type: [Player],
      args: {
        filter: t.arg({ type: PlayerFilter }),
      },
      resolve: (_parent, { filter }) =>
        [...Players.values()].filter(
          (player) => !filter?.position || player.position === filter.position,
        ),
    }),
  }),
});

The fields shape works like a patch over the imported type: return null for a field to drop it, return a field ref to add or replace one, and every field you don't mention is imported unchanged. Everything else Pothos can carry over (description, deprecation reasons, isTypeOf/resolveType, extensions, default values, and isOneOf on inputs) comes across automatically from the source type.

The type methods

Each method takes the source GraphQLType plus optional options, and returns the matching ref:

MethodSource typeReturns
addGraphQLObject<Shape>GraphQLObjectTypeObjectRef<Shape>
addGraphQLInterface<Shape>GraphQLInterfaceTypeInterfaceRef<Shape>
addGraphQLUnion<Shape>GraphQLUnionTypeUnionRef<Shape>
addGraphQLEnum<Shape>GraphQLEnumTypeEnumRef<Shape>
addGraphQLInput<Shape>GraphQLInputObjectTypeInputObjectRef<Shape>

Every method's options are the standard options for that type kind, minus the part derived from the source, plus a name to rename the type as you import it:

  • Objects and interfaces take everything an object or interface type takes except fields, which is replaced by the patch-style shape above (fieldName: null to drop, a ref to add or replace). In practice the options that take effect are name and extensions (merged into the source's). description, isTypeOf/resolveType, and interfaces always come from the source type, so set those on the source rather than passing them here.
  • Unions take a types override to spell out the members yourself instead of importing the source's.
  • Enums take a values override to remap the values.
  • Inputs take the same patch-style fields shape as objects.

If the source type isn't already typed with a generic, cast it to the expected kind so the method's Shape binds, for example addGraphQLObject<IPlayer>(schema.getType('Player') as GraphQLObjectType).

const NodeRef = builder.addGraphQLInterface<INode>(LegacyNode, {
  name: 'Entity',
});

const SearchResult = builder.addGraphQLUnion<ITeam | IPlayer>(LegacySearchResult);

const Division = builder.addGraphQLEnum<'EAST' | 'WEST'>(LegacyDivision);

Scalars

There's no addGraphQLScalar method, because Pothos already has one. Register an existing scalar with the core builder.addScalarType:

builder.addScalarType('DateTime', LegacyDateTime);

Scalars imported through the add option or referenced by name from the Scalars generic work the same way; the dedicated method just exists for the ref-returning case.