Modules that help make GraphQL servers that work with Fanout Cloud.
See fanout/apollo-serverless-demo for an example project that uses this to power a GraphQL API server with GraphQL Subscriptions on AWS Lambda.
Fanout Cloud can act as a reverse proxy between your users' web browsers and your GraphQL API, holding open long-running WebSocket connections so your server (or function-as-a-service) doesn't have to. Instead, Fanout Cloud makes simple regular HTTP Requests to your application using the WebSocket-Over-HTTP Protocol. The tools in this library allow your GraphQL API server to serve the GraphQL Subscriptions protocol (graphql-ws
) over WebSocket-Over-HTTP.
Let's say you already have a project that uses apollo-server to make a GraphQL API with subscriptions. Follow these steps to make it work with Fanout.
-
Make some decisions about your data stores. These tools require a persistent place to store data of two types: GraphQL PubSub Subscriptions as well as WebSocket-Over-HTTP Connections. You must provide storage objects that implement the ISimpleTable interface.
-
You can make your own and store data wherever you want.
-
This interface is a subset of the
@pulumi/cloud.Table
interface, so you can use those too. Pulumi has implementations for AWS DyanmoDB as well as Azure Table Storage. -
If you want to use another data store not listed here and need help, file an issue to let us know.
-
When developing, you can use
MapSimpleTable
, which stores data in-memory in aMap
object. But the data won't be very persistent.Here's an example of creating these storage objects using
MapSimpleTable
.import { MapSimpleTable, IStoredPubSubSubscription, IStoredConnection } from "fanout-graphql-tools" const connectionStorage = MapSimpleTable<IStoredConnection>() const pubSubSubscriptionStorage = MapSimpleTable<IStoredPubSubSubscription>()
-
-
Use
WebSocketOverHttpContextFunction
when constructingApolloServer
. This adds some properties to your GraphQL Context that can later be used in your GraphQL Resolvers.import { WebSocketOverHttpContextFunction } from "fanout-graphql-tools" // you may get ApolloServer from elsewhere, e.g. apollo-server-express import { ApolloServer } from "apollo-server" import { makeExecutableSchema } from "graphql-tools"; import MyGraphqlApi from "./my-graphql-api" // these depend on your specific API, e.g. https://github.com/apollographql/apollo-server#installation-standalone const { typeDefs, resolvers } = MyGraphqlApi() const schema = makeExecutableSchema({ typeDefs, resolvers }) const apolloServer = ApolloServer({ context: WebSocketOverHttpContextFunction({ grip: { // Get this from your Fanout Cloud console, which looks like https://api.fanout.io/realm/{realm-id}?iss={realm-id}&key=base64:{realm-key} // or use this localhost for your own pushpin.org default installation url: process.env.GRIP_URL || "http://localhost:5561", }, pubSubSubscriptionStorage, schema, }), schema, })
You can see a full example of this here
-
In your GraphQL Resolvers, wrap all usages of
pubsub
withWebSocketOverHttpPubSubMixin(context)(pubsub)
.Every
ApolloServer
has to be created with some GraphQL Resolvers. To power GraphQL Subscriptions, these resolvers make use of aPubSubEngine
. In mutation resolvers, you callpubsub.publish(triggerName, payload)
. In your subscription resolvers, you callpubsub.asyncIterator(triggerName)
.Here's a before/after example
-
Before (example from the official Apollo docs on subscriptions.)
const resolvers = { Subscription: { postAdded: { // Additional event labels can be passed to asyncIterator creation subscribe: () => pubsub.asyncIterator([POST_ADDED]), }, }, Mutation: { addPost(root: any, args: any, context: any) { pubsub.publish(POST_ADDED, { postAdded: args }); return postController.addPost(args); }, }, };
-
After wrapping pubsubs with
WebSocketOverHttpPubSubMixin(context)(pubsub)
import { WebSocketOverHttpPubSubMixin } from "fanout-graphql-tools" const resolvers = { Subscription: { postAdded: { // Additional event labels can be passed to asyncIterator creation subscribe: (source, args, context) => WebSocketOverHttpPubSubMixin(context)(pubsub).asyncIterator([POST_ADDED]), }, }, Mutation: { addPost(root: any, args: any, context: any) { WebSocketOverHttpPubSubMixin(context)(pubsub).publish(POST_ADDED, { postAdded: args }); return postController.addPost(args); }, }, };
You can see a full example of this in SimpleGraphqlApi
-
-
Add WebSocket-Over-HTTP handling to the http server that serves your GraphQL App. The way to do this depends on how you make an HTTP Server.
-
apollo-server-express
Many projects use
ApolloServer
along with the Express web framework. There is an official apollo-server integration called apollo-server-express. You can add WebSocket-Over-HTTP handling to your epxress app withGraphqlWsOverWebSocketOverHttpExpressMiddleware
.import * as express from "express" import { GraphqlWsOverWebSocketOverHttpExpressMiddleware } from "fanout-graphql-tools" import { makeExecutableSchema } from "graphql-tools"; import MyGraphqlApi from "./my-graphql-api" // these depend on your specific API, e.g. https://github.com/apollographql/apollo-server#installation-standalone const { typeDefs, resolvers } = MyGraphqlApi() const schema = makeExecutableSchema({ typeDefs, resolvers }) const app = express() .use(GraphqlWsOverWebSocketOverHttpExpressMiddleware({ // we created these earlier, remember? connectionStorage, pubSubSubscriptionStorage, schema, })) // later, do `ApolloServer(/*...*/).applyMiddleware({ app })
You can see a full example of this here
-
Other web frameworks
Not everyone uses express. That's fine. We still want to work with your project. Many node.js web frameworks ultimately end up using the
http
module from the standard library behind the scenes. If your web framework gives you a reference to an underlyinghttp.Server
instance, you can useGraphqlWsOverWebSocketOverHttpSubscriptionHandlerInstaller
to install WebSocket-Over-HTTP handling to it.import * as http from "http" import { GraphqlWsOverWebSocketOverHttpSubscriptionHandlerInstaller } from "fanout-graphql-tools" import { makeExecutableSchema } from "graphql-tools"; import MyGraphqlApi from "./my-graphql-api" // these depend on your specific API, e.g. https://github.com/apollographql/apollo-server#installation-standalone const { typeDefs, resolvers } = MyGraphqlApi() const schema = makeExecutableSchema({ typeDefs, resolvers }) // However you get here, e.g. with https://github.com/zeit/micro const httpServer: http.Server = http.createServer(requestListener) GraphqlWsOverWebSocketOverHttpSubscriptionHandlerInstaller({ connectionStorage, pubSubSubscriptionStorage, schema, })(httpServer);
Take a look at the micro example for a working example of this with an http.Server created from micro and apollo-server-micro.
-
Have a question about this part? File an issue and we can help out and add to the docs.
-
Those are the steps for using fanout-graphql-tools. See apollo-serverless-demo for a fully functional app, running in AWS Lambda and storing data in DynamoDB.
Release a new version of this package by pushing a git tag with a name that is a semver version like "v0.0.2".
Make sure you also update the package.json
to have the same version.
The best way to do this is using npm version <newversion>
, which will update package.json
, then create a git commit, then create a git tag pointing to that git commit. You should run this in the master branch.
After that you can push the commit and new tags using git push --follow-tags
.
npm version minor
git push --follow-tags
Travis is configured to test and publish all git tags to npm. You don't need to run npm publish
locally.