Apollo Server 2 is officially end-of-life as of 22 October 2023.

Learn more about upgrading.

API Reference: graphql-tools


The graphql-tools library enables the creation and manipulation of GraphQL schema. Apollo Server is able to accept a schema that has been enabled by graphql-tools. Apollo server directly exports all the function from graphql-tools, enabling a migration path for more complicated use cases.

Apollo Server includes graphql-tools version 4. To use another version of the library, see Using a different version of graphql-tools.

JavaScript
1const { makeExecutableSchema } = require('apollo-server');
2
3const typeDefs = gql`
4  type Query {
5    hello: String
6  }
7`;
8
9const resolvers = {
10  Query: {
11    hello: () => 'Hello world!'
12  },
13};
14
15const schema = makeExecutableSchema({
16  typeDefs,
17  resolvers,
18});
19
20const rootResolveFunction = (parent, args, context, info) => {
21  //perform action before any other resolvers
22};
23
24addSchemaLevelResolveFunction(schema, rootResolveFunction)
25
26const server = new ApolloServer({ schema });
27
28// normal ApolloServer listen call but url will contain /graphql
29server.listen().then(({ url }) => {
30  console.log(`🚀 Server ready at ${url}`)
31});

makeExecutableSchema(options)

makeExecutableSchema takes a single argument: an object of options. Only the typeDefs option is required.

Text
1const { makeExecutableSchema } = require('apollo-server');
2
3const jsSchema = makeExecutableSchema({
4  typeDefs,
5  resolvers, // optional
6  logger, // optional
7  allowUndefinedInResolve = false, // optional
8  resolverValidationOptions = {}, // optional
9  directiveResolvers = null, // optional
10  schemaDirectives = null,  // optional
11  parseOptions = {},  // optional
12  inheritResolversFromInterfaces = false  // optional
13});
  • typeDefs is a required argument and should be a GraphQL schema language string or array of GraphQL schema language strings or a function that takes no arguments and returns an array of GraphQL schema language strings. The order of the strings in the array is not important, but it must include a schema definition.

  • resolvers is an optional argument (empty object by default) and should be an object that follows the pattern explained in the resolvers documentation.

  • logger is an optional argument, which can be used to print errors to the server console that are usually swallowed by GraphQL. The logger argument should be an object with a log function, eg. const logger = { log: e => console.log(e) }

  • parseOptions is an optional argument which allows customization of parse when specifying typeDefs as a string.

  • allowUndefinedInResolve is an optional argument, which is true by default. When set to false, causes your resolve functions to throw errors if they return undefined, which can help make debugging easier.

  • resolverValidationOptions is an optional argument which accepts an ResolverValidationOptions object which has the following boolean properties:

    • requireResolversForArgs will cause makeExecutableSchema to throw an error if no resolve function is defined for a field that has arguments.

    • requireResolversForNonScalar will cause makeExecutableSchema to throw an error if a non-scalar field has no resolver defined. Setting this to true can be helpful in catching errors, but defaults to false to avoid confusing behavior for those coming from other GraphQL libraries.

    • requireResolversForAllFields asserts that all fields have a valid resolve function.

    • requireResolversForResolveType will require a resolveType() method for Interface and Union types. This can be passed in with the field resolvers as __resolveType(). False to disable the warning.

    • allowResolversNotInSchema turns off the functionality which throws errors when resolvers are found which are not present in the schema. Defaults to false, to help catch common errors.

  • inheritResolversFromInterfaces GraphQL Objects that implement interfaces will inherit missing resolvers from their interface types defined in the resolvers object.

addMockFunctionsToSchema(options)

JavaScript
1const { addMockFunctionsToSchema } = require('apollo-server');
2
3addMockFunctionsToSchema({
4  schema,
5  mocks: {},
6  preserveResolvers: false,
7});

Given an instance of GraphQLSchema and a mock object, addMockFunctionsToSchema modifies the schema in place to return mock data for any valid query that is sent to the server. If mocks is not passed, the defaults will be used for each of the scalar types. If preserveResolvers is set to true, existing resolve functions will not be overwritten to provide mock data. This can be used to mock some parts of the server and not others.

MockList(list, mockFunction)

JavaScript
1const { MockList } = require('apollo-server');
2
3new MockList(length: number | number[], mockFunction: Function);

This is an object you can return from your mock resolvers which calls the mockFunction once for each list item. The first argument can either be an exact length, or an inclusive range of possible lengths for the list, in case you want to see how your UI responds to varying lists of data.

addResolveFunctionsToSchema({ schema, resolvers, resolverValidationOptions?, inheritResolversFromInterfaces? })

addResolveFunctionsToSchema takes an options object of IAddResolveFunctionsToSchemaOptions and modifies the schema in place by attaching the resolvers to the relevant types.

JavaScript
1const { addResolveFunctionsToSchema } = require('apollo-server');
2
3const resolvers = {
4  RootQuery: {
5    author(obj, { name }, context) {
6      console.log("RootQuery called with context " +
7        context + " to find " + name);
8      return Author.find({ name });
9    },
10  },
11};
12
13addResolveFunctionsToSchema({ schema, resolvers });

The IAddResolveFunctionsToSchemaOptions object has 4 properties that are described in makeExecutableSchema.

TypeScript
1export interface IAddResolveFunctionsToSchemaOptions {
2  schema: GraphQLSchema;
3  resolvers: IResolvers;
4  resolverValidationOptions?: IResolverValidationOptions;
5  inheritResolversFromInterfaces?: boolean;
6}

addSchemaLevelResolveFunction(schema, rootResolveFunction)

Some operations, such as authentication, need to be done only once per query. Logically, these operations belong in an obj resolve function, but unfortunately GraphQL-JS does not let you define one. addSchemaLevelResolveFunction solves this by modifying the GraphQLSchema that is passed as the first argument.

delegateToSchema

The delegateToSchema method can be found on the info.mergeInfo object within any resolver function, and should be called with the following named options:

Text
1delegateToSchema(options: {
2  schema: GraphQLSchema;
3  operation: 'query' | 'mutation' | 'subscription';
4  fieldName: string;
5  args?: { [key: string]: any };
6  context: { [key: string]: any };
7  info: GraphQLResolveInfo;
8  transforms?: Array<Transform>;
9}): Promise<any>

schema: GraphQLSchema

A subschema to delegate to.

operation: 'query' | 'mutation' | 'subscription'

The operation type to use during the delegation.

fieldName: string

A root field in a subschema from which the query should start.

args: { [key: string]: any }

Additional arguments to be passed to the field. Arguments passed to the field that is being resolved will be preserved if the subschema expects them, so you don't have to pass existing arguments explicitly, though you could use the additional arguments to override the existing ones. For example:

GraphQL
1# Subschema
2
3type Booking {
4  id: ID!
5}
6
7type Query {
8  bookingsByUser(userId: ID!, limit: Int): [Booking]
9}
10
11# Schema
12
13type User {
14  id: ID!
15  bookings(limit: Int): [Booking]
16}
17
18type Booking {
19  id: ID!
20}

If we delegate at User.bookings to Query.bookingsByUser, we want to preserve the limit argument and add an userId argument by using the User.id. So the resolver would look like the following:

JavaScript
1const resolvers = {
2  User: {
3    bookings(parent, args, context, info) {
4      return info.mergeInfo.delegateToSchema({
5        schema: subschema,
6        operation: 'query',
7        fieldName: 'bookingsByUser',
8        args: {
9          userId: parent.id,
10        },
11        context,
12        info,
13      });
14    },
15    ...
16  },
17  ...
18};

context: { [key: string]: any }

GraphQL context that is going to be passed to subschema execution or subscription call.

info: GraphQLResolveInfo

GraphQL resolve info of the current resolver. Provides access to the subquery that starts at the current resolver.

Also provides the info.mergeInfo.delegateToSchema function discussed above.

transforms: Array

Transforms to apply to the query and results. Should be the same transforms that were used to transform the schema, if any. After transformation, transformedSchema.transforms contains the transforms that were applied.

Additional considerations - Aliases

Delegation preserves aliases that are passed from the parent query. However that presents problems, because default GraphQL resolvers retrieve field from parent based on their name, not aliases. This way results with aliases will be missing from the delegated result. mergeSchemas and transformSchemas go around that by using src/stitching/defaultMergedResolver for all fields without explicit resolver. When building new libraries around delegation, one should consider how the aliases will be handled.

mergeSchemas

TypeScript
1mergeSchemas({
2  schemas: Array<string | GraphQLSchema | Array<GraphQLNamedType>>;
3  resolvers?: Array<IResolvers> | IResolvers;
4  onTypeConflict?: (
5    left: GraphQLNamedType,
6    right: GraphQLNamedType,
7    info?: {
8      left: {
9        schema?: GraphQLSchema;
10      };
11      right: {
12        schema?: GraphQLSchema;
13      };
14    },
15  ) => GraphQLNamedType;
16})

This is the main function that implements schema stitching. Read below for a description of each option.

schemas

schemas is an array of GraphQLSchema objects, schema strings, or lists of GraphQLNamedTypes. Strings can contain type extensions or GraphQL types, which will be added to resulting schema. Note that type extensions are always applied last, while types are defined in the order in which they are provided.

resolvers

resolvers accepts resolvers in same format as makeExecutableSchema. It can also take an Array of resolvers. One addition to the resolver format is the possibility to specify a fragment for a resolver. The fragment must be a GraphQL fragment definition string, specifying which fields from the parent schema are required for the resolver to function properly.

JavaScript
1resolvers: {
2  Booking: {
3    property: {
4      fragment: 'fragment BookingFragment on Booking { propertyId }',
5      resolve(parent, args, context, info) {
6        return mergeInfo.delegateToSchema({
7          schema: bookingSchema,
8          operation: 'query',
9          fieldName: 'propertyById',
10          args: {
11            id: parent.propertyId,
12          },
13          context,
14          info,
15        });
16      },
17    },
18  },
19}

mergeInfo and delegateToSchema

The info.mergeInfo object provides the delegateToSchema method:

JavaScript
1type MergeInfo = {
2  delegateToSchema<TContext>(options: IDelegateToSchemaOptions<TContext>): any;
3}
4
5interface IDelegateToSchemaOptions<TContext = {
6    [key: string]: any;
7}> {
8    schema: GraphQLSchema;
9    operation: Operation;
10    fieldName: string;
11    args?: {
12        [key: string]: any;
13    };
14    context: TContext;
15    info: GraphQLResolveInfo;
16    transforms?: Array<Transform>;
17}

As described in the documentation above, info.mergeInfo.delegateToSchema allows delegating to any GraphQLSchema object, optionally applying transforms in the process. See Built-in transforms.

onTypeConflict

JavaScript
1type OnTypeConflict = (
2  left: GraphQLNamedType,
3  right: GraphQLNamedType,
4  info?: {
5    left: {
6      schema?: GraphQLSchema;
7    };
8    right: {
9      schema?: GraphQLSchema;
10    };
11  },
12) => GraphQLNamedType;

The onTypeConflict option to mergeSchemas allows customization of type resolving logic.

The default behavior of mergeSchemas is to take the first encountered type of all the types with the same name. If there are conflicts, onTypeConflict enables explicit selection of the winning type.

For example, here's how we could select the last type among multiple types with the same name:

JavaScript
1const onTypeConflict = (left, right) => right;

And here's how we might select the type whose schema has the latest version:

JavaScript
1const onTypeConflict = (left, right, info) => {
2  if (info.left.schema.version >= info.right.schema.version) {
3    return left;
4  } else {
5    return right;
6  }
7}

When using schema transforms, onTypeConflict is often unnecessary, since transforms can be used to prevent conflicts before merging schemas. However, if you're not using schema transforms, onTypeConflict can be a quick way to make mergeSchemas produce more desirable results.

Transform

TypeScript
1interface Transform = {
2  transformSchema?: (schema: GraphQLSchema) => GraphQLSchema;
3  transformRequest?: (request: Request) => Request;
4  transformResult?: (result: Result) => Result;
5};
6
7type Request = {
8  document: DocumentNode;
9  variables: Record<string, any>;
10  extensions?: Record<string, any>;
11};
12
13type Result = ExecutionResult & {
14  extensions?: Record<string, any>;
15};

transformSchema

Given a GraphQLSchema and an array of Transform objects, produce a new schema with those transforms applied.

Delegating resolvers will also be generated to map from new schema root fields to old schema root fields. Often these automatic resolvers are sufficient, so you don't have to implement your own.

Built-in transforms

Built-in transforms are ready-made classes implementing the Transform interface. They are intended to cover many of the most common schema transformation use cases, but they also serve as examples of how to implement transforms for your own needs.

Modifying types

  • FilterTypes(filter: (type: GraphQLNamedType) => boolean): Remove all types for which the filter function returns false.

  • RenameTypes(renamer, options?): Rename types by applying renamer to each type name. If renamer returns undefined, the name will be left unchanged. Options controls whether built-in types and scalars are renamed. Root objects are never renamed by this transform.

TypeScript
1RenameTypes(
2  (name: string) => string | void,
3  options?: {
4    renameBuiltins: Boolean;
5    renameScalars: Boolean;
6  },
7)

Modifying root fields

  • TransformRootFields(transformer: RootTransformer): Given a transformer, arbitrarily transform root fields. The transformer can return a GraphQLFieldConfig definition, a object with new name and a field, null to remove the field, or undefined to leave the field unchanged.

TypeScript
1TransformRootFields(transformer: RootTransformer)
2
3type RootTransformer = (
4  operation: 'Query' | 'Mutation' | 'Subscription',
5  fieldName: string,
6  field: GraphQLField<any, any>,
7) =>
8  | GraphQLFieldConfig<any, any>
9  | { name: string; field: GraphQLFieldConfig<any, any> }
10  | null
11  | void;
  • FilterRootFields(filter: RootFilter): Like FilterTypes, removes root fields for which the filter function returns false.

TypeScript
1FilterRootFields(filter: RootFilter)
2
3type RootFilter = (
4  operation: 'Query' | 'Mutation' | 'Subscription',
5  fieldName: string,
6  field: GraphQLField<any, any>,
7) => boolean;
  • RenameRootFields(renamer): Rename root fields, by applying the renamer function to their names.

TypeScript
1RenameRootFields(
2  renamer: (
3    operation: 'Query' | 'Mutation' | 'Subscription',
4    name: string,
5    field: GraphQLField<any, any>,
6  ) => string,
7)

Other

  • ExractField({ from: Array<string>, to: Array<string> }) - move selection at from path to to path.

  • WrapQuery( path: Array<string>, wrapper: QueryWrapper, extractor: (result: any) => any, ) - wrap a selection at path using function wrapper. Apply extractor at the same path to get the result. This is used to get a result nested inside other result

JavaScript
1transforms: [
2  // Wrap document takes a subtree as an AST node
3  new WrapQuery(
4    // path at which to apply wrapping and extracting
5    ['userById'],
6    (subtree: SelectionSetNode) => ({
7      // we create a wrapping AST Field
8      kind: Kind.FIELD,
9      name: {
10        kind: Kind.NAME,
11        // that field is `address`
12        value: 'address',
13      },
14      // Inside the field selection
15      selectionSet: subtree,
16    }),
17    // how to process the data result at path
18    result => result && result.address,
19  ),
20],
  • ReplaceFieldWithFragment(targetSchema: GraphQLSchema, mapping: FieldToFragmentMapping): Replace the given fields with an inline fragment. Used by mergeSchemas to handle the fragment option.

TypeScript
1type FieldToFragmentMapping = {
2  [typeName: string]: { [fieldName: string]: InlineFragmentNode };
3};

delegateToSchema transforms

The following transforms are automatically applied by delegateToSchema during schema delegation, to translate between new and old types and fields:

  • AddArgumentsAsVariables: Given a schema and arguments passed to a root field, make those arguments document variables.

  • FilterToSchema: Given a schema and document, remove all fields, variables and fragments for types that don't exist in that schema.

  • AddTypenameToAbstract: Add __typename to all abstract types in the document.

  • CheckResultAndHandleErrors: Given a result from a subschema, propagate errors so that they match the correct subfield. Also provide the correct key if aliases are used.

By passing a custom transforms array to delegateToSchema, it's possible to run additional transforms before these default transforms, though it is currently not possible to disable the default transforms.

Feedback

Edit on GitHub

Forums