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-graphqlimport 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:
| Method | Source type | Returns |
|---|---|---|
addGraphQLObject<Shape> | GraphQLObjectType | ObjectRef<Shape> |
addGraphQLInterface<Shape> | GraphQLInterfaceType | InterfaceRef<Shape> |
addGraphQLUnion<Shape> | GraphQLUnionType | UnionRef<Shape> |
addGraphQLEnum<Shape> | GraphQLEnumType | EnumRef<Shape> |
addGraphQLInput<Shape> | GraphQLInputObjectType | InputObjectRef<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: nullto drop, a ref to add or replace). In practice the options that take effect arenameandextensions(merged into the source's).description,isTypeOf/resolveType, andinterfacesalways come from the source type, so set those on the source rather than passing them here. - Unions take a
typesoverride to spell out the members yourself instead of importing the source's. - Enums take a
valuesoverride to remap the values. - Inputs take the same patch-style
fieldsshape 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.