Your First Federated Schema with Apollo
Tags: graphql, javascript, apollo
Published: 2020-07-21 14:00:39

*The following post is based on the code I demoed during my Apollo Space Camp talk. [You can find the recording of that talk here.](https://youtu.be/v_1bn2sHdk4)*

---

GraphQL APIs shine in their ability to expose a single data graph to client applications. What's more, they also make it easy for those clients to query only the data they need and in a way that reflects the relationships between the nodes in the graph.

However, as we express more and more objects and their relationships through types and fields, it doesn't take long for even a moderately complex app to require a large number of type definitions. In turn, it becomes increasingly difficult for multiple people or teams to collaborate on building the API.

When the time comes to move to a distributed GraphQL architecture, [Apollo Federation](https://www.apollographql.com/docs/apollo-server/federation/introduction/) provides a set of tools to split a large schema into smaller implementing services based on _separation of concerns_, rather than by types alone. 

And in contrast to other approaches for creating distributed GraphQL architectures such as schema stitching, Apollo Federation also offers a straightforward, declarative interface to help compose each of the _federated schemas_ back into a single data graph for clients to query.

In this post, I will walk through how to:

- Transform a regular Apollo Server into one using Apollo Federation
- Create two implementing services that each manage a portion of the overall schema
- Extend and reference types across services

## Getting Started

Let's begin by setting up a basic Apollo Server. Our first step will be to create a project directory:

```sh
mkdir apollo-federation-demo && cd apollo-federation-demo
```

Next, we'll create a `package.json` file:

```sh
npm init --yes
```

Now we can install the packages we need to set up our GraphQL API:

```sh
npm i apollo-server concurrently graphql json-server node-fetch nodemon
```

In this demo, we'll spin up a mocked REST API using [JSON Server](https://github.com/typicode/json-server) to act as the backing data source for the GraphQL API. The REST API will have `/astronauts` and `/missions` endpoints where we can query data about various Apollo-era astronauts and their missions.

To set up a basic Apollo Server with a JSON Server REST API, we'll need to create two files in our project directory:

```sh
touch index.js db.json
```

You can [**copy and paste this data**](https://raw.githubusercontent.com/mandiwise/space-camp-federation-demo/master/db.json) to use in the new `db.json` file. Note that the `crew` field for each mission is an array of IDs that refer to individual astronauts.

In the `index.js` file, we'll then add the following code:

```js
const { ApolloServer, gql } = require("apollo-server");
const fetch = require("node-fetch");

const port = 4000;
const apiUrl = "http://localhost:3000";

const typeDefs = gql`
  type Astronaut {
    id: ID!
    name: String
  }
  type Query {
    astronaut(id: ID!): Astronaut
    astronauts: [Astronaut]
  }
`;

const resolvers = {
  Query: {
    astronaut(_, { id }) {
      return fetch(`${apiUrl}/astronauts/${id}`).then(res => res.json());
    },
    astronauts() {
      return fetch(`${apiUrl}/astronauts`).then(res => res.json());
    }
  }
};

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

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

Our basic GraphQL schema currently contains two queries to fetch all astronauts or a single astronaut by their unique ID (we'll add more queries to get mission data shortly).

To start up our API, we'll need to add a few scripts to the `package.json` file:

```json
{
  // ...
  "scripts": {
    "server": "concurrently -k npm:server:*",
    "server:rest": "json-server -q db.json",
    "server:graphql": "nodemon index.js"
  },
	// ...
}
```

With this code in place, we can run `npm run server` to start up the API. If you like, you can test out one of the astronaut queries now in GraphQL Playground at `http://localhost:4000`.

## Create the Astronauts Service

To refactor our Apollo Server into one using Apollo Federation, we'll start by adding two more packages:

```sh
npm i @apollo/federation @apollo/gateway
```

The `@apollo/federation` package will allow us to make our services’ schemas federation-ready and `@apollo/gateway` will help us compose the separate schemas into a single data graph and then distribute incoming GraphQL API requests to underlying services. We still need the `apollo-server` package installed because we will use an instance of `ApolloServer` for the gateway API and each of the implementing services we create.

Now we'll create a separate file to manage the astronauts service:

```sh
touch astronauts.js
```

The `astronauts.js` file will end up looking very similar to what's inside our current `index.js` file. We'll start by adding the required packages and constants at the top of this file:

```js
const { ApolloServer, gql } = require("apollo-server");
const { buildFederatedSchema } = require("@apollo/federation");
const fetch = require("node-fetch");

const port = 4001;
const apiUrl = "http://localhost:3000";
```

You'll notice that we import the `buildFederatedSchema` function from the Apollo Federation package above. This function will allow us to make our astronauts schema federation-ready. We also set the `port` number to `4001` here because this service will need a dedicated port (and we will continue to use port `4000` for the client-facing gateway API).

Before we move the astronaut-related type definitions into this file, we'll need to familiarize ourselves with the notion of an _entity_ in a federated data graph. An entity is a type that you define canonically in one implementing service and then reference and extend in other services. Entities are the core building blocks of a federated graph and we create them using the `@key` directive in our schema.

To that end, we'll add a `@key` directive to the `Astronaut` type definition when we move the `typeDefs` and `resolvers` to `astronauts.js`. This directive is the way we tell Apollo that `Astronaut` can be referenced and extended by other services (as long as the other services can identify an astronaut by the value represented by their ID field):

```js
// ...

const typeDefs = gql`
  type Astronaut @key(fields: "id")
    id: ID!
    name: String
  }

  extend type Query {
    astronaut(id: ID!): Astronaut
    astronauts: [Astronaut]
  }
`;

const resolvers = {
  Query: {
    astronaut(_, { id }) {
      return fetch(`${apiUrl}/astronauts/${id}`).then(res => res.json());
    },
    astronauts() {
      return fetch(`${apiUrl}/astronauts`).then(res => res.json());
    }
  }
};
```

In the code above, you may have also noticed that we use the `extend` keyword now in front of `type Query`. The `Query` and `Mutation` types originate at the gateway level of the API, so the Apollo documentation says that all implementing services should "extend" these types with any additional operations. The resolvers for the astronauts schema will look exactly as they did our original Apollo Server.

Next, instead of passing the `typeDefs` and `resolvers` into the `ApolloServer` constructor directly, we will instead set a `schema` option to the return value of calling `buildFederatedSchema` with the `typeDefs` and `resolvers` passed in. We also update the `console.log` statement so it's clear astronauts service is starting:

```js
// ...

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

server.listen({ port }).then(({ url }) => {
  console.log(`Astronauts service ready at ${url}`);
});
```

Our complete `astronauts.js` file will now look like this:

```js
const { ApolloServer, gql } = require("apollo-server");
const { buildFederatedSchema } = require("@apollo/federation");
const fetch = require("node-fetch");

const port = 4001;
const apiUrl = "http://localhost:3000";

const typeDefs = gql`
  type Astronaut @key(fields: "id") {
    id: ID!
    name: String
  }
  extend type Query {
    astronaut(id: ID!): Astronaut
    astronauts: [Astronaut]
  }
`;

const resolvers = {
  Query: {
    astronaut(_, { id }) {
      return fetch(`${apiUrl}/astronauts/${id}`).then(res => res.json());
    },
    astronauts() {
      return fetch(`${apiUrl}/astronauts`).then(res => res.json());
    }
  }
};

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

server.listen({ port }).then(({ url }) => {
  console.log(`Astronauts service ready at ${url}`);
});
```

Now we'll need to make some changes in `index.js` to turn that Apollo Server into the gateway of our GraphQL API. Our refactored `index.js` file will look like this:

```js
const { ApolloServer } = require("apollo-server");
const { ApolloGateway } = require("@apollo/gateway");

const port = 4000;

const gateway = new ApolloGateway({
  serviceList: [{ name: "astronauts", url: "http://localhost:4001" }]
});

const server = new ApolloServer({
  gateway,
  subscriptions: false
});

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

To recap, we've made the following updates to `index.js`:

- We deleted the `gql` and `fetch` imports, as well as the `apiUrl`, `typeDefs`, and `resolvers` constants (because we only need these in `astronaut.js` now)
- We instantiated a new `ApolloGateway` and added the astronauts service to it
- We updated the `ApolloServer` constructor by removing the `typeDefs` and `resolvers` that were previously passed directly into it, and then we added the `gateway` to it instead and set the `subscriptions` option to `false` because subscription operations are not supported by Apollo Federation at this time

Lastly, before we can start up our new gateway API, we'll need to add a start script for the astronauts service in `package.json`:

```json
{
  // ...
  "scripts": {
    "server": "concurrently -k npm:server:*",
    "server:rest": "json-server -q db.json",
    "server:astronauts": "nodemon astronauts.js",
    "server:graphql": "nodemon index.js"
  },
  // ...
}
```

Once again, we can run `npm run server` and test out our API in GraphQL Playground at `http://localhost:4000`. We'll know everything is working if the API returns data from the astronaut queries exactly as it did before.

## Add a Missions Service

Now that we have our astronauts service up and running, we can create a second implementing service to handle the missions-related data. First, we'll create a `missions.js` file:

```bash
touch missions.js
```

Next, we'll scaffold the code in `missions.js` much like the astronauts service:

```jsx
const { ApolloServer, gql } = require("apollo-server");
const { buildFederatedSchema } = require("@apollo/federation");
const fetch = require("node-fetch");

const port = 4002;
const apiUrl = "<http://localhost:3000>";

const typeDefs = gql``;

const resolvers = {};

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

server.listen({ port }).then(({ url }) => {
  console.log(`Missions service ready at ${url}`);
});
```

We will also define a `Mission` type, its basic queries, and all of the required resolvers in `missions.js`:

```jsx
// ...

const typeDefs = gql`
  type Mission {
    id: ID!
    designation: String!
    startDate: String
    endDate: String
  }

  extend type Query {
    mission(id: ID!): Mission
    missions: [Mission]
  }
`;

const resolvers = {
  Query: {
    mission(_, { id }) {
      return fetch(`${apiUrl}/missions/${id}`).then(res => res.json());
    },
    missions() {
      return fetch(`${apiUrl}/missions`).then(res => res.json());
    }
  }
};

// ...
```

Now for the fun part! It's time to make a connection between the two different services using the `Astronaut` entity. Specifically, we're going to add a `crew` field to the `Mission` type that returns a list of `Astronaut` objects.

This is possible thanks to another key federation concept—once an entity is defined in one service we can _reference_ it from other services as needed. To use the `Astronaut` type with the `Mission` type's `crew` field, we'll need to update `missions.js` as follows:

```jsx
// ...

const typeDefs = gql`
  type Mission {
    id: ID!
    crew: [Astronaut]
    designation: String!
    startDate: String
    endDate: String
  }

  extend type Astronaut @key(fields: "id") {
    id: ID! @external
  }

  # ...
`;

// ...
```

In the code above, we include the `Astronaut` type again so we can use it in this service, but this time we put the `extend` keyword in front of it. We must also include its key field of `id` inside the definition and add the `@external` directive to it to indicate that this field was defined in another service. 

Our code won't work quite yet because we still need to create a resolver for the new `crew` field. When resolving the `crew` field the only information the missions service will have about the corresponding astronauts is their unique IDs, but that's OK!

To resolve these fields with Apollo Federation, we only need to return an object (or in our case, a list of objects that represent each of the crew members) containing the `__typename` and the `id` key field that identifies the astronaut:

```jsx
// ...

const resolvers = {
  Mission: {
    crew(mission) {
      return mission.crew.map(id => ({ __typename: "Astronaut", id }));
    }
  },
  // ...
};

// ...
```

The gateway will hand off these representations of the entities to the astronaut's service to be fully resolved, so we also need a way to resolve these references once they reach the originating service. To do that, we must provide a _reference resolver_ for the `Astronaut` type in `astronauts.js` to fetch the data for a given entity based in it `id` key:

```jsx
// ...

const resolvers = {
  Astronaut: {
    __resolveReference(ref) {
      return fetch(`${apiUrl}/astronauts/${ref.id}`).then(res => res.json());
    }
  },
  // ...
};

// ...
```

We're now ready to add the missions service to the gateway in `index.js`:

```jsx
// ...

const gateway = new ApolloGateway({
  serviceList: [
    { name: "astronauts", url: "http://localhost:4001" },
    { name: "missions", url: "http://localhost:4002" }
  ]
});

// ...
```

And in `package.json`, we'll add another start script for the missions service too:

```json
{
  // ...
  "scripts": {
    "server": "concurrently -k npm:server:*",
    "server:rest": "json-server -q db.json",
    "server:astronauts": "nodemon astronauts.js",
    "server:mission": "nodemon missions.js",
    "server:graphql": "nodemon index.js"
  },
  // ...
}
```

When we run `npm run server` again, we'll see that we can now query missions with related crew data in GraphQL Playground:

```graphql
query {
  missions {
    designation
    crew {
      name
    }
  }
}
```

As a finishing touch, it would great if we could traverse the graph in the other direction too. To that end, we're going to add a `missions` field to get a list of related `Mission` objects when querying astronauts. 

When adding a `missions` field to the `Astronaut` type, we won't need to touch any of our existing code in `astronauts.js` and we will also get to see a final key federation concept in action. From with the referencing missions service, we can _extend_ the `Astronaut` type with an additional field.

In `missions.js`, we'll update our extended `Astronaut` type:

```jsx
// ...

const typeDefs = gql`
  # ...

  extend type Astronaut @key(fields: "id") {
    id: ID! @external
    missions: [Mission]
  }

  # ...
`;

// ...
```

Lastly, we have to resolve the new field from within the missions service too. Inside the `missions` field resolver, again, we only have access to the data about astronauts that exists within the context of this service. In other words, we only have access to the astronauts' unique IDs.

Due to the limitations of how data can be queried from the mocked REST API, we'll have to settle for fetching all of the mission data and then filtering out the mission objects that don't contain a given astronaut's ID (we're dealing with a small amount of data here, so this will be OK for our demonstration purposes):

```jsx
// ...

const resolvers = {
  Astronaut: {
    async missions(astronaut) {
      const res = await fetch(`${apiUrl}/missions`);
      const missions = await res.json();

      return missions.filter(({ crew }) =>
        crew.includes(parseInt(astronaut.id))
      );
    }
  },
  // ...
};

// ...
```

Back over in GraphQL Playground, we can now query an astronaut's with their mission data too:

```graphql
query {
  astronauts {
    name
    missions {
      designation
    }
  }
}
```

## Conclusion

Congratulations! You just created your first federated data graph using Apollo Federation. We covered a lot of ground in this tutorial, including:

- How to create a federated data graph, two implementing services, and an `Astronaut` entity
- How to reference the `Astronaut` entity in the missions service and use it for the `crew` field on the `Mission` type
- How to extend the `Astronaut` entity in the missions service and add a `missions` field to it so we can traverse these relationships in both directions through the graph

I hope this post has given you a glimpse of how approachable Apollo Federation is if you have a bit of prior experience with Apollo Server. For more details about what's possible with the Apollo Federation and Apollo Gateway packages, be sure to [visit the official docs](https://www.apollographql.com/docs/apollo-server/federation/introduction/).

You can also find the [complete code for this tutorial on GitHub](https://github.com/mandiwise/space-camp-federation-demo) and read more about building full-stack JavaScript applications in my book [Advanced GraphQL with Apollo & React](https://8bit.press/book/advanced-graphql).

---

Photo credit: [NASA](https://unsplash.com/photos/pd4lo70LdbI)