GraphQL module definition

Front-Commerce’s GraphQL modules is the mechanism allowing to extend and override any part of the schema defined by other modules.

To get started with GraphQL modules, we recommend you to read the Extend the GraphQL schema documentation page.

A Front-Commerce GraphQL module has to export an object containing its definition. This page contains the API different keys available in a GraphQL module’s definition.

namespace

The simplest (but useless) GraphQL module definition only requires an unique namespace key:

// A minimal Front-Commerce GraphQL module that basically does nothing
export default {
  namespace: "Acme/HelloWorld"
};

This namespace can be used by other modules as a dependency (see dependencies), to ensure another module has been registered in the application.

modules (optional)

The modules key is useful when creating a meta module. It allows to declare the submodules to be registered when this module is registered.

It should be an array with a list of submodules to be included in the application.

Example:

import Core from "./core";
import FeatureA from "./feature-a";
import FeatureB from "./feature-b";

export default {
  namespace: "Acme/All",
  modules: [Core, FeatureA, FeatureB]
};

Dependency management: submodules dependencies will be resolved independently.

It means that if a meta module A declares A1 and A2 in its modules key, and A1 depends on module B then there is no guarantee that A2 will be initialized after B.

You should make dependencies explicit either in the meta module definition or in each submodule definition. Our goal is to make sure that each module can be used separately as far as possible. Thus, each dependency should be added to the module needing it and not in a meta module.

dependencies (optional)

A list of module namespaces (see namespace) which must have been registered in the application to allow this module to work. Dependencies will be initialized before this module.

Example:

export default {
  namespace: "Acme/HelloWorld",
  dependencies: ["Acme/Core"]
  // …
};
In depth: modules are sorted using the toposort library. See flattenAndReorderModulesUsingDependencies code and tests for further details.

typeDefs (optional)

GraphQL type definitions provided by this module. As a developer, this is a contract with the rest of your codebase.

Type definitions must be GraphQL Schema Definition Language strings, and can be declared inline or loaded from a .gql file.

You can create new types, add top level queries (by extending the Query type), mutations or extend types from other modules.

// or import typeDefs from './schema.gql';
const typeDefs = `
extend type Query {
  "Be polite!"
  sayHello (name: String!): Message
}

type Message {
  response: String
  audio: String
}
`;

export default {
  namespace: "Acme/HelloWorld",
  typeDefs: typeDefs
};
In depth: under the hood, all module’s typeDefs are merged in a single array passed to graphql-tools’ makeExecutableSchema function

resolvers (optional)

This is an object containing all the resolvers providing data for the fields defined in the schema (usually from your typeDefs). This is where the implementation resides.

Resolver map must follow the format documented in GraphQL Tools.

// or import resolvers from './resolvers.js';
const resolvers = {
  Query: {
    sayHello: (_, { name }) => {
      const message = `Hello ${name}`;
      return {
        message: message,
        audio: `https://tts.service.com/q=${message}`
      };
    }
  }
};

export default {
  namespace: "Acme/HelloWorld",
  // …
  resolvers: resolvers
};
In depth: under the hood, all module’s resolvers are merged in a single object (using lodash.merge passed to graphql-tools’ makeExecutableSchema function. It is possible to override resolvers declared in another module by declaring it as a dependency and registering a local resolver.

contextEnhancer (optional)

The contextEnhancer module definition key should be a function that initializes code that will be made available in GraphQL’s context, under the loaders key. It is specific to Front-Commerce.

This is where you could construct models or dataloaders that should be used in your GraphQL resolvers. See Slim down resolvers with loaders for more information.

The contextEnhancer function must return an object whose keys will be merged with previous modules’. It will be passed a single argument with the following keys:

  • req: the current server request
  • loaders: current loaders (from module initialized beforehand)
  • makeDataLoader: a factory to build a dataloader (see makeDataLoader usage)
  • config: the global configuration

Example:

import MessageLoader from "./loader";

export default {
  namespace: "Acme/HelloWorld",
  resolvers: {
    Query: {
      sayHello: (_, { name }, { loaders }) => {
        return loaders.Message.load(`Hello ${name}`);
      }
    }
  },
  contextEnhancer: ({ req, loaders, makeDataLoader, config }) => {
    return {
      Message: MessageLoader(makeDataLoader)(config.apiBaseUrl)
    }
  }
}

remoteSchema (optional)

The remoteSchema key allows you to achieve remote schema stitching in Front-Commerce.

It should be an object with the following keys.

uri

The uri key is mandatory and must contain the remote GraphQL endpoint.

Example:

export default {
  namespace: "Acme/RemoteFeature",
  remoteSchema: {
    uri: "https://remote-feature.acme.org/graphql"
  }
};

By default all queries and mutations are merged with the current schema. A set of default transformations are applied: read the dedicated documentation section for further information.

transforms (optional)

The transforms key allows you to optionally manipulate the remote schema before it is stitched with the existing Front-Commerce schema.

It must be an array of valid graphql-tools Schema Transforms, and will be applied before Front-Commerce’s default transforms.

Example:

import { FilterRootFields } from "@graphql-tools/wrap";

export default {
  namespace: "Acme/RemoteFeature",
  remoteSchema: {
    uri: "https://remote-feature.acme.org/graphql"
    transforms: [
      new FilterRootFields(
        (operation, rootField) =>
          operation === "Query" && rootField === "aRootFieldToExpose"
      )
    ]
  }
};

executor (optional)

This feature has been added in version 2.0.0

The executor key allows you to optionally modify the underlying executor for each request sent to the remote schema.

For instance it allows you to add new headers to your requests if the remote schema needs them. This is usually the case when there is an authentication system in your remote service.

You can create your own executor from scratch by following Creating an executor from GraphQL Tools or use the makeExecutor helper available in Front-Commerce.

The following example demonstrates how to add an Authorization header to your requests.

import makeExecutor from "server/core/graphql/makeExecutor";

const authenticateRequest = () => (fetchOptions, { context }) => {
  const req = (context && context.req) || {};
  const authService = makeAuthServiceFromRequest(req);
  if (!authService.isAuthenticated()) {
    return fetchOptions;
  }

  return {
    ...fetchOptions,
    headers: {
      ...(fetchOptions.headers || {}),
      Authorization: `Bearer ${authService.getAuthToken()}`,
    },
  };
};

// […]
export default {
  namespace: "Acme/RemoteFeature",
  remoteSchema: {
    uri: "https://remote-feature.acme.org/graphql",
    // […]
      executor: makeExecutor(uri, {
        fetchOptionsAdapter: withMagentoAuthorizationHeaders(),
      }),
  }
};

Deprecated fields

linkContextBuilders (optional)

This feature has been added in version 1.0.0-beta.3 and is deprecated since 2.0.0

This option will be ignored if executor option is defined.

The linkContextBuilders key allows you to optionally modify the underlying Apollo’s HTTP Link context for each request. Please note that the HTTP Link context is different from the GraphQL Context (even if they share the same term!).

It should be a list of functions that will enrich the context with the value they return.

One of the most common usage for instance would be to authenticate remote requests by adding a Authorization header to requests. Context builders functions will receive the Front-Commerce GraphQL context so they could implement a wide range of logic based on the current HTTP Request or loaders.

Example:

// […]
const authenticateRequest = context => {
  const authService = makeAuthServiceFromRequest(context.req || {});
  if (authService.isAuthenticated()) {
    return {
      headers: {
        Authorization: `Bearer ${authService.getAuthToken()}`
      }
    };
  }
  return {};
};

// […]
export default {
  namespace: "Acme/RemoteFeature",
  remoteSchema: {
    uri: "https://remote-feature.acme.org/graphql",
    // […]
    linkContextBuilders: [authenticateRequest]
  }
};

apolloLinkHttpOptions (optional)

_This feature has been added in version 2.0.0-rc.0 and is deprecated since 2.0.0__

This option will be ignored if executor option is defined.

The apolloLinkHttpOptions key allows you to customize options passed to the apollo-link-http that fetches the schema.

This is especially useful if the remote GraphQL schema is using the GET method for GraphQL queries.

Example:

// […]
export default {
  namespace: "Acme/RemoteFeature",
  remoteSchema: {
    uri: "https://remote-feature.acme.org/graphql",
    // […]
    apolloLinkHttpOptions: {
      useGETForQueries: true
    }
  }
};
Edit on GitHub