/
Launch Apollo Studio

Implementing services


This article describes how to create an implementing service for a federated data graph using Apollo Server.

Defining an implementing service

To be part of a federated graph, an implementing service must conform to the Apollo Federation specification, which exposes the service's capabilities to the gateway, as well as to tools like Apollo Studio.

Converting an existing schema into an implementing service is the first step in building a federated graph. To start, here's a non-federated Apollo Server setup:

index.js
const { ApolloServer, gql } = require('apollo-server');

const typeDefs = gql`
  type Query {
    me: User
  }

  type User {
    id: ID!
    username: String
  }
`;

const resolvers = {
  Query: {
    me() {
      return { id: "1", username: "@ava" }
    }
  }
};

const server = new ApolloServer({
  typeDefs,
  resolvers,
});

server.listen(4001).then(({ url }) => {
    console.log(`🚀 Server ready at ${url}`);
});

This should look familiar if you've set up Apollo Server before. If it doesn't, we recommend you familiarize yourself with the basics before jumping into federation.

Now, let's convert this to an implementing service. The first step is to install the @apollo/federation package in our project:

npm install @apollo/federation

Defining an entity

As part of our federated architecture, we want other implementing services to be able to extend the User type this service defines. To enable this, we add the @key directive to the User type's definition to designate it as an entity:

index.js
const { ApolloServer, gql } = require('apollo-server');
const { buildFederatedSchema } = require('@apollo/federation');

const typeDefs = gql`
  type Query {
    me: User
  }

  type User @key(fields: "id") {
    id: ID!
    username: String
  }
`;

The @key directive tells other services which field(s) of the User type to use to uniquely identify a particular instance. In this case, services should use the single field id.

Next, we add a reference resolver for the User type. A reference resolver tells the gateway how to fetch an entity by its @key fields:

index.js
const resolvers = {
  Query: {
    me() {
      return { id: "1", username: "@ava" }
    }
  },
  User: {
    __resolveReference(user, { fetchUserById }){
      return fetchUserById(user.id)
    }
  }
};

(This example requires defining the fetchUserById function to obtain the appropriate User from our backing data store.)

Learn more about entities

Generating a federated schema

Finally, we use the buildFederatedSchema function from the @apollo/federation package to augment our schema definition with federation support. We provide the result of this function to the ApolloServer constructor:

index.js
const server = new ApolloServer({
  schema: buildFederatedSchema([{ typeDefs, resolvers }])
});

server.listen(4001).then(({ url }) => {
    console.log(`🚀 Server ready at ${url}`);
});

The server is now ready to act as an implementing service in a federated data graph!

Combined example

Here are the snippets above combined (again, note that for this sample to be complete, you must define the fetchUserById function for your data source):

index.js
const { ApolloServer, gql } = require('apollo-server');
const { buildFederatedSchema } = require('@apollo/federation');

const typeDefs = gql`
  type Query {
    me: User
  }

  type User @key(fields: "id") {
    id: ID!
    username: String
  }
`;

const resolvers = {
  Query: {
    me() {
      return { id: "1", username: "@ava" }
    }
  },
  User: {
    __resolveReference(user, { fetchUserById }){
      return fetchUserById(user.id)
    }
  }
}

const server = new ApolloServer({
  schema: buildFederatedSchema([{ typeDefs, resolvers }])
});

server.listen(4001).then(({ url }) => {
    console.log(`🚀 Server ready at ${url}`);
});

Defining custom directives

The method for defining custom directives differs slightly in Apollo Federation.

Without Apollo Federation, you provide your directive definitions to the constructor of ApolloServer in the schemaDirectives argument, like so:

With Apollo Federation, you instead call SchemaDirectiveVisitor.visitSchemaDirectives, passing in your schema and your directives, before you provide your schema to the constructor of ApolloServer:

const { ApolloServer, gql, SchemaDirectiveVisitor } = require('apollo-server');
const { buildFederatedSchema } = require ('@apollo/federation')

// typeDefs and resolvers defined here

class DeprecatedDirective extends SchemaDirectiveVisitor {
  public visitFieldDefinition(field: GraphQLField<any, any>) {
    field.isDeprecated = true;
    field.deprecationReason = this.args.reason;
  }
}

const directives = {
  deprecated: DeprecatedDirective
};
let schema = buildFederatedSchema([{ typeDefs, resolvers }]);

SchemaDirectiveVisitor.visitSchemaDirectives(schema, directives);

const server = new ApolloServer({
  schema: schema
});

Also make sure to read about the gateway's support for custom directives.

Securing implementing services

Because of the power and flexibility of Apollo Federation's _entities field, your implementing services should not be directly accessible by clients. Instead, only your gateway should have access to your implementing services. Clients then communicate with the gateway:

Web app
iOS app
Gateway
Products service
Reviews service
Inventory service

Make sure to implement any necessary firewall rules, access control lists, or other measures to ensure that individual implementing services can be accessed only via the gateway.

Edit on GitHub