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
module.exports = {
  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";

module.exports = {
  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:

module.exports = {
  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
}
`;

module.exports = {
  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 for resolving fields defined in the schema (usually from your typeDefs). This is where the implementation resides.

Resolver map must follow the format documented in Apollo 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}`
      };
    }
  }
};

module.exports = {
  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
  • config: the global configuration

Example:

const MessageLoader = require("./loader");

module.exports = {
  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:

module.exports = {
  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:

const { FilterRootFields } = require("graphql-tools");

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

linkContextBuilders (optional)

This feature has been added in version 1.0.0-beta.3

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 {};
};

// […]
module.exports = {
  namespace: "Acme/RemoteFeature",
  remoteSchema: {
    uri: "https://remote-feature.acme.org/graphql",
    // […]
    linkContextBuilders: [authenticateRequest]
  }
};
Edit on GitHub