Building a GraphQL server with GraphQL Helix 🧬
Tags: graphql, node, javascript, typescript
Published: 2020-11-05 19:58:14

Earlier this week I released [GraphQL Helix](https://github.com/contrawork/graphql-helix), a new JavaScript library that lets you take charge of your GraphQL server implementation.

There's a couple of factors that pushed me to roll my own GraphQL server library:

* I wanted to use bleeding-edge GraphQL features like `@defer`, `@stream` and `@live` directives.
* I wanted to make sure I wasn't tied down to a specific framework or runtime environment.
* I wanted control over how server features like persisted queries were implemented.
* I wanted to use something other than WebSocket (i.e. SSE) for subscriptions.

Unfortunately, popular solutions like [Apollo Server](https://github.com/apollographql/apollo-server), [express-graphql](https://github.com/graphql/express-graphql) and [Mercurius](https://github.com/mercurius-js/mercurius) fell short in one or more of these regards, so here we are.

Existing libraries like Apollo Server provide you with either a complete HTTP server or else a middleware function that you can plug into your framework of choice. GraphQL Helix takes a  different approach -- it just provides a handful of functions that you can use to turn an HTTP request into a GraphQL execution result. In other words, GraphQL Helix leaves it up to _you_ to decide how to send back the response.

Let's see how this works in practice.

## A Basic Example

We'll start by building an express application and adding a `/graphql` endpoint.

```js
import express from "express";
import { schema } from "./my-awesome-schema";

const app = express();

app.use(express.json());

app.use("/graphql", async (res, req) => {
  // TODO
});

app.listen(8000);
```

Note that we're assuming here we already have a GraphQL schema we've created. However you build your schema ([GraphQL Tools](https://github.com/ardatan/graphql-tools), [TypeGraphQL](https://github.com/MichalLytek/type-graphql), 
[graphql-compose](https://github.com/graphql-compose/graphql-compose), [GraphQL Nexus](https://github.com/graphql-nexus/schema), etc.) is irrelevant -- as long as you have a GraphQLSchema object, you're good to go.

Next, let's extract the relevant bits from our request into a standard GraphQL Helix object:

```js
app.use("/graphql", async (res, req) => {
  const request = {
    body: req.body,
    headers: req.headers,
    method: req.method,
    query: req.query,
  };
});
```

More astute readers might note that we could have just used the `req` object as-is — and that's true! However, this step will look a little different depending on the framework or runtime we use, so I'm being more explicit about how we define this object.

Now let's extract the relevant parameters from the request and process them.

```js
import {
  getGraphQLParameters,
  processRequest
} from "graphql-helix";

...

app.use("/graphql", async (res, req) => {
  const request = {
    body: req.body,
    headers: req.headers,
    method: req.method,
    query: req.query,
  };

  const {
    query,
    variables,
    operationName
  } = getGraphQLParameters(request);

  const result = await processRequest({
    schema,
    query,
    variables,
    operationName,
    request,
  })
});
```

`processRequest` still takes our `Request` object as a parameter, so why doesn't it just call `getGraphQLParameters` for us? As we'll see later on, this is an intentional design choice that gives us the flexibility to decide how the parameters are actually derived from the request.

So, we've processed our request and now have a result. Groovy. Let's do something with that result.

```js
app.use("/graphql", async (res, req) => {
  const request = {
    body: req.body,
    headers: req.headers,
    method: req.method,
    query: req.query,
  };

  const {
    query,
    variables,
    operationName
  } = getGraphQLParameters(request);

  const result = await processRequest({
    schema,
    query,
    variables,
    operationName,
    request,
  })

  if (result.type === "RESPONSE") {
    result.headers.forEach(({ name, value }) => {
      res.setHeader(name, value)
    });
    res.status(result.status);
    res.json(result.payload);
  } else {
    // TODO
  }
});
```

Our result includes the headers we should send back, an HTTP status code and the response payload (i.e. an object containing the `data` and `errors` we get by actually validating and executing the request).

And that's it! We now have a working `/graphql` endpoint that can process our requests. Neat.

So why are we writing all this extra boilerplate when I could do the same thing in a few lines of code in Apollo Server? In a word: flexibility. If we swap out Express for another framework like Fastify, we only have to change how we construct our request object and how we handle the result. In fact, we could use the meat of our implementation in virtually any other runtime -- serverless, Deno or even in the browser.

Moreover, we can process the result however our business needs dictate. We have a [GraphQL over HTTP specification](https://github.com/graphql/graphql-over-http), but if for some reason you need to deviate from it, you can. It's your application -- send back the status, headers or response that are right for your use case.

So... what's up with that `else` block? As it turns out, `processRequest` will return one of three types of results:

* `RESPONSE` for standard queries and mutations,
* `MULTIPART_RESPONSE` for requests that include the new `@defer` and `@stream` directives, and
* `PUSH` for subscriptions

Again, it's up to us to implement how to send back these responses, so let's do that now!

## Subscriptions

We'll implement our subscriptions using Server Sent Events (SSE). There's a lot of advantages of using SSE over something like WebSockets for subscriptions, like being able to use the same middleware for all your requests, but a deeper comparison of the two approaches will be the topic of a future article.

There's a few libraries out there that can make integrating SSE with Express easier, but we'll do it from scratch for this example:

```js
if (result.type === "RESPONSE") {
  ...
} else if (result.type === "PUSH") {
  res.writeHead(200, {
    "Content-Type": "text/event-stream",
    Connection: "keep-alive",
    "Cache-Control": "no-cache",
  });

  req.on("close", () => {
    result.unsubscribe();
  });

  await result.subscribe((result) => {
    res.write(`data: ${JSON.stringify(result)}\n\n`);
  });
}
```

Here, our result includes two methods -- `subscribe` and `unsubscribe`. We call `subscribe` with a callback that's passed the result each time a new subscription event is pushed -- within this callback, we just `write` to the response with a SSE-compatible payload. And we call `unsubscribe` when the request is closed (i.e. when the client closes the connection) to prevent memory leaks.

Easy, peasy. Now let's take a look at `MULTIPART_RESPONSE`.

## Multipart Responses

If our request includes `@stream` or `@defer` directives, our request needs to be sent down to the client in chunks. For example, with `@defer`, we send down everything _except_ the deferred fragment and eventually send down the deferred fragment data when its finally resolved. As such, our `MULTIPART_RESPONSE` result looks a lot like the `PUSH` result with one key difference -- we do want to eventually end our response once all parts have been sent.

```js
if (result.type === "RESPONSE") {
  ...
} else if (result.type === "PUSH") {
  ...
} else {
  res.writeHead(200, {
    Connection: "keep-alive",
    "Content-Type": 'multipart/mixed; boundary="-"',
    "Transfer-Encoding": "chunked",
  });

  req.on("close", () => {
    result.unsubscribe();
  });

  await result.subscribe((result) => {
    const chunk = Buffer.from(
      JSON.stringify(result),
      "utf8"
    );
    const data = [
      "",
      "---",
      "Content-Type: application/json; charset=utf-8",
      "Content-Length: " + String(chunk.length),
      "",
      chunk,
      "",
    ].join("\r\n");
    res.write(data);
  });

  res.end("\r\n-----\r\n");  
}
```

Note that the Promise returned by `subscribe` won't resolve until the request has been fully resolved and the callback has been called with all the chunks, at which point we can safely end our response.

Congrats! Our API now has support for `@defer` and `@stream` (provide you're using the correct version of `graphql-js`).

## Adding GraphiQL

GraphQL Helix comes with two additional functions that can be used to expose a GraphiQL interface on your server. 

`shouldRenderGraphiQL` takes a Request object and returns a boolean that indicates, as you may have already guessed, whether you should render the interface. This is helpful when you have a single endpoint for both your API and the interface and only want to return the GraphiQL interface when processing a GET request from inside a browser.

`renderGraphiQL` just returns a string with the HTML necessary to the render the interface. If you want to create a separate endpoint for your documentation, you can use this function without using `shouldRenderGraphiQL` at all.

```js
app.use("/graphql", async (req, res) => {
  const request = {
    body: req.body,
    headers: req.headers,
    method: req.method,
    query: req.query,
  };

  if (shouldRenderGraphiQL(request)) {
    res.send(renderGraphiQL());
  } else {
    // Process the request
  }
});
```

The returned GraphiQL has a fetcher implementation that will work with multipart requests and SSE as shown in the examples above. If you need to do something else for your server, you can roll your own using `renderGraphiQL` as a template only.

## Evolving your server implementation

GraphQL Helix is, by design, light-weight and unopinionated. Libraries like Apollo Server are bloated with a lot of features that you may never need.


![Screen Shot 2020-11-05 at 11.22.49 AM](https://dev-to-uploads.s3.amazonaws.com/i/yxmzfvzs3nrt4k7ujz2w.png)
 

However, that doesn't mean you can't add those features back if you need them. For example, we can add uploads to our server by adding the Upload scalar and using the appropriate middleware from [graphql-upload](https://github.com/jaydenseric/graphql-upload)

```js
import { graphqlUploadExpress } from "graphql-upload";

app.use(
  "/graphql",
  graphqlUploadExpress({
    maxFileSize: 10000000,
    maxFiles: 10,
  }),
  (req, res) => {
    // Our implementation from before
  }
)
```

Similarly, we can add support for live queries with the `@live` directive by adding [@n1ru4l/graphql-live-query](https://github.com/n1ru4l/graphql-live-query) and [@n1ru4l/in-memory-live-query-store](https://github.com/n1ru4l/in-memory-live-query-store). We just need to add the directive to our schema and provide the appropriate `execute` implementation:

```js
import {
  InMemoryLiveQueryStore
} from "@n1ru4l/in-memory-live-query-store";

const liveQueryStore = new InMemoryLiveQueryStore();

...

const result = const result = await processRequest({
  schema,
  query,
  variables,
  operationName,
  request,
  execute: liveQueryStore.execute,
});
```

Tracing, logging, persisted queries, request batching, [response deduplication](https://github.com/gajus/graphql-deduplicator) and any number of other features can be added just as easily without the bloat and without having to wrestle with some plugin API or unfriendly abstraction.

You can check the repository for more examples and recipes (I'll be adding more as time allows and also accepting PRs!).

## Conclusion

So when should you use Apollo Server instead of GraphQL Helix? If you need to throw together a quick POC or tutorial, Apollo Server is great. If you want to use federation, you might want to stick with Apollo (and even then there are [better alternatives to doing GraphQL with microservices](https://product.voxmedia.com/2020/11/2/21494865/to-federate-or-stitch-a-graphql-gateway-revisited)).

GraphQL Helix offers a flexible, extensible approach to building a GraphQL server, without the bloat. If you're building something other than another to-do tutorial, I highly recommend checking it out :)