Build Or Clone — A Federated API — Pt. 1: The Gateway ++

Nick McLean
Dev Genius
Published in
9 min readFeb 18, 2022

--

Hello All, Thanks for joining in for what I am calling “Build or Clone.” So you can build it with me, or clone the finished repo! We are creating a fully functional Apollo Federated API. This is Part 1 — The Gateway ++.

Today, I’d love to share part of a project that I have been working on for the past few months — An Apollo Gateway that has a little more kick than the average tutorial/starter repo.

The Gateway has a variety of responsibilities other than directing the average graphql request to the subgraph. This gateway, regardless if you choose to build it yourself or clone the end result as a starter, fulfills the responsibilities that might get overlooked when creating your own gateway.

So, if you are interested in learning a bit about the repo, the responsibilities of a gateway, or how to code a Gateway, read on!

And for those of you who want to just clone the fully functional, end result, repo, then skip to the bottom to learn how. I made it so that people can use it as a starter for their own gateway or just to be run as is!

Let’s start The Gateway.

Person stepping through a fictional portal, representing a gateway

Apollo Gateway — What We Are Building

A Gateway is the entry point to your microservice API within a federated architecture. All requests that are made to the API need to go through the Gateway, which means that this small service has a lot of responsibility.

To fetch a User from a Users Micro Service, the client will send the request to the Gateway, which then will find its way to the Users Service to fulfill the request.

The Benefits

Using a Gateway provides quite a few of benefits, as opposed to querying the external services directly.

  1. One Single Endpoint — No matter how many services there are, the client will only need to know about one single endpoint and server location.
  2. Global Context — Global context can be added to all requests at the Gateway level. Typed custom data can now be passed to all microservices.
  3. Global Authorization — Rather than having to parse authorization on every microservice, authorization can now be parsed globally at the Gateway level.

The Responsibilities

This means that the gateway will have quite a bit of inferred responsibilities as well.

  1. Passing the Request to the Correct Subgraph
  2. Type Checking the Incoming GraphQL Requests
  3. Handling Authorization
  4. Attaching Context to Requests
  5. Handling Requests for File Uploads(Cause what modern application doesn’t accept file uploads?)
  6. Serving The Uploaded Files/Static Assets

Hopefully, you get the idea! It’s important to set up a Gateway that does what we need it to do for a modern API.

So we are going to build a Gateway for an API that handles these 6 responsibilities and 3 benefits outlined above.

Getting Set Up

Create and Initialize

Create the gateway directory for the project and init with npm.

mkdir gateway-tutorial
cd gateway-tutorial
npm init -y

The examples below will be using typescript, but setting this up is a bit outside of the scope of this article. Be sure to get your tsconfig set up the way you like.

Once you have done that let’s create the src file and the entry-point.

mkdir src
touch server.ts

The Entry Point — Server.ts

Express Setup

The Gateway will need to be able to take both GraphQL Requests as well as requests for static assets (RESTful Requests).

To handle the express setup we will need a few packages.

npm i express cors

Express will be perfect for handling the serving of static assets. Start by creating the boilerplate for a simple express app.

// server.tsimport { express } from "express";
import cors from "cors";
const app = express();
app.use(cors());
app.use(express.json());
const port = 5000;app.listen(port, () => console.log(`GATEWAY ====> UP ON PORT ${port}`));

Create The Gateway

We will be able to create the gateway with the @apollo/gateway package.

The @the-devoyage/micro-auth-helpers the package provides helper functions to generate context, pass headers, and parse authorization status and more. Install this package from the GitHub Registry as shown below, or check out the Micro Auth Helper Docs.

Be sure to add the scope to your .npmrc and login to the GitHub Registry.

## Login to GitHub registry
npm login --registry=https://npm.pkg.github.com
## Tell NPM where to find `@the-devoyage` packages.
echo @the-devoyage:registry=https://npm.pkg.github.com >> .npmrc
## Install The Packages
npm i @apollo/gateway @the-devoyage/micro-auth-helpers

Then create the Gateway

// server.tsimport { ApolloGateway } from "@apollo/gateway";
import { Helpers } from "@the-devoyage/micro-auth-helpers";
import { readFileSync } from "fs";
// ...imports
// Express Setup...const supergraphSdl = readFileSync("./supergraph.graphql").toString();const gateway = new ApolloGateway({
supergraphSdl: supergraphSdl ?? "",
buildService({ url }) {
const dataSource = new Helpers.Gateway.ContextDataSource({ url });
return dataSource;
},
});

The supergraphSdl is a schema of all the types throughout the entire API. In basic terms, it defines what can or can’t happen in your gateway. This will be generated with Rover CLI in an upcoming step, so don’t worry about the actual file for now — just make sure to read it like above.

Within the new ApolloGateway instance, the buildService the function allows us to add data to the requested outgoing to subgraphs. This means we can generate global context — data that originates in the gateway but is passed to all connected subgraphs.

You can extend RemoteGraphQLDataSource, from the package @apollo/gateway, to create your own global context — or — The @the-devoyage/micro-auth-helpers package helps us do this with the helper ContextDataSource, as shown in the code example above.

In addition to helping us create global context, the ContextDataSource helper also extends file uploading capabilities to external APIs through GraphQL Requests using the graphql-upload package. We will talk about this below!

Create and Start the Apollo Server

It’s time to create the Apollo Server, and we will need another few packages to do so.

npm i apollo-server-express graphql-upload dotenv

And create the server:

// server.tsimport { Helpers } from "@the-devoyage/micro-auth-helpers";
import { ApolloServer } from "apollo-server-express";
import { graphqlUploadExpress } from "graphql-upload";
import dotenv from "dotenv";
// ...imports
// ...Express Setup// ...Gateway Setupdotenv.config(); // Get Environment Variables// Create and Start Apollo Serverlet apolloServer;async function startServer() {
apolloServer = new ApolloServer({
gateway,
context: ({ req }) =>
Helpers.Gateway.GenerateContext({
headers: ["Authorization"],
req,
secretOrPublicKey: process.env.JWT_ENCRYPTION_KEY,
}),
});
await apolloServer.start();
app.use(graphqlUploadExpress());
apolloServer.applyMiddleware({ app });
}
startServer();// Start Express Serverapp.listen(port, () => console.log(`GATEWAY ====> UP ON PORT ${port}`));

The async function allows us to await the start of the apolloServer, apply the graphql-upload middleware, and finally apply the express application to the apolloServer.

The GenerateContext helper is going to allow us to create a context for the server, and more importantly, auth context.

The first way to generate context with this helper is to pick and choose headers of the incoming request to use as the context within the gateway server. This is incredibly useful, as we will need to check the Authorization header for a JWT to grant authorization to the server. To pick a header to include as context, add it to the headers array, as I did with Authorization above.

The neat thing about the helper is that it does a bit more than it leads on. In our case, since we are using a JWT and an Authorization header, it is also going to decode the JWT payload and add the result to the context. We don’t have to do more than supply the JWT_ENCRYPTION_KEY, as a parameter to the function.

So, simply by using GenerateContext and ContextDataSource we are able to create a global auth the context for our API.

// Global Contextexport interface Context extends Record<string, any> {
auth: AuthContext;
// ...other custom context!
}
export interface AuthContext {
payload: Payload;
isAuth: boolean;
error?: string;
}

Check out the Package Itself:

And here is an article that I wrote that outlines this package’s features:

Handle Static File Requests

Next, we will need to make sure restful requests still can come through the gateway server even if a client is requesting a file such as an image.

The Gateway will need to receive the request and proxy it to the appropriate server which will handle the request.

First, install the proxy software:

npm i http-proxy-middleware 

Create a route to handle the proxy:

// server.ts
import { createProxyMiddleware } from "http-proxy-middleware";
// ...imports
// Express Setup...app.use(
process.env.MEDIA_SERVING_ROUTE ?? "/public",
createProxyMiddleware({
target: process.env.MEDIA_SERVER_URL,
changeOrigin: false,
})
);

Instead of hard coding the route and server location, we can use environment variables. This will allow you to switch the route or server URL between production and development environments.

Now, all requests to the gateway that go to the endpoint specified in the environment variables, will get redirected to the specified server where we can handle finding and serving the image.

Starting The Server

Start the server with nodemon or by creating an npm script if you like, or just give it a try the old-fashioned way — Don’t forget, if you are using typescript, to compile it first.

node server.js

We should see the server come alive and the gateway can now start receiving requests!

GATEWAY ====> UP ON PORT 5000

The gateway is started but won’t be that useful yet as there is no supergraph to enable type checking or routing.

From this part of the tutorial and forward, you will need some federated subgraphs up and running to continue. If you are at a lack of subgraphs laying around, then hold tight — This is Part 1 of a 5 part series! Next week we are going to be creating an Accounts Subgraph. Be sure to follow along, if you are interested.

Install Rover and Generate a Supergraph

First, choose your method of installation from the Apollo Docs to install the Rover CLI. I’ll do it with curl below:

curl -sSL https://rover.apollo.dev/nix/latest | sh

Now create a config file, saved to the root of the project. This file will tell the Gateway about each service. I called mine supergraph-config.yaml.

subgraphs:
accounts:
routing_url: http://localhost:5001
schema:
subgraph_url: http://localhost:5001
users:
routing_url: http://localhost:5002
schema:
subgraph_url: http://localhost:5002
meida:
routing_url: http://localhost:5006/graphql
schema:
subgraph_url: http://localhost:5006/graphql
mailer:
routing_url: http://mailer:5008/graphql
schema:
subgraph_url: http://localhost:5008/graphql

While all Subgraphs are running, we can now use the Rover CLI to generate a Supergraph.

rover supergraph compose --config ./supergraph-config.yaml > supergraph.graphql

If all the type checking is successful, it saves the output to the file we defined above, supergraph.graphql.

Restart The Gateway and Celebrate

This is it, we are just about done. Anytime the subgraph typings change, the Supergraph will need to be re-generated — AND — anytime that the Supergraph gets regenerated, you will need to restart the Gateway.

Once the gateway is restarted, you should be able to query the Subgraphs!

The Gateway — The Repository

Hey! You just built a Robust Gateway that handles a variety of request scenarios… or wait, are you the guy that skipped to the bottom! Either way, check out this gateway!

  • Type checks all subgraphs
  • Routes graphql requests to the correct subgraph using Rover CLI/supergraph
  • Generates gateway context from a variety of sources such as request headers, JWTs, or custom data
  • Global Context — context is sent to all external micro services
  • Automatically creates a typed Authorization Context for every request
  • Proxy restful requests to and from an external micro server, to request static assets from a file storage micro service.
  • It passes files within graphql requests to subgraphs — for use with graphql-upload

Clone The Repository

I made this repository dual purpose — Use it as a starter template to customize your own gateway or launch it and use it as the gateway out of the box for your next project, it will suit most needs.

Purchase Access — I use Basetools to add a paywall to my git repo! Once your transaction goes through, you’ll get an email to join as a collaborator within the repo. This means you can clone it as much as you like and use it as you wish. You also get access to all future releases!

Don’t want to purchase? That’s ok — become a follower and message me and I’ll add you as a collaborator for that type of support too!

Thanks for checking out this tutorial on creating an Apollo Gateway. I hope it helps you get going with your next project!

Keep tuned and follow as there are 4 more tutorials/repos coming along for 4 graphql federated micro services! We are going to be building an Accounts Microservice, a Users Micro Service, a File Upload Micro Service, and finally an automated emailing micro service!

--

--

Hello all! Join in for some web development, hiking, dogs, food, craft beer and jazz talk — but really just web development!