/aws-lambda-graphql

GraphQL with subscriptions in AWS Lambda

Primary LanguageTypeScriptMIT LicenseMIT

AWS Lambda GraphQL with subscriptions

CircleCI aws-lambda-graphql package version aws-lambda-ws-link package version

As of version 0.8.0 you can use subscriptions-transport-ws client.

This library uses typescript so for now use types as documentation

This library uses yarn and yarn workspaces so only yarn.lock is commited.

Motivation

This library is created because I wanted to try if it's possible to implement GraphQL server with subscriptions in AWS Lambda. And yes it is possible with AWS API Gateway v2.

Table of contents

Infrastructure

Current infrastructure is implemented using AWS Lambda + AWS API Gateway v2 + AWS DynamoDB (with DynamoDB streams). But it can be implemented using various solutions (Kinesis, etc) because it's written to be modular.

Usage (server)

npm install aws-lamda-graphql apollo-link graphql graphql-subscriptions
# or
yarn add aws-lambda-graphql apollo-link graphql graphql-subscriptions

Implement your simple server

const {
  createDynamoDBEventProcessor,
  createWsHandler,
  DynamoDBConnectionManager,
  DynamoDBEventStore,
  DynamoDBSubscriptionManager,
  PubSub,
} = require('aws-lambda-graphql');
import { makeExecutableSchema } from 'graphql-tools';

// this only processes AWS Api Gateway v2 events
// if you want to process HTTP too, use createHttpHandler
// or you can use both, see chat-example-server

// instantiate event store
// by default uses Events table (can be changed)
const eventStore = new DynamoDBEventStore();
const pubSub = new PubSub({ eventStore });
const subscriptionManager = new DynamoDBSubscriptionManager();
const connectionManager = new DynamoDBConnectionManager({
  subscriptions: subscriptionManager,
});

const schema = makeExecutableSchema({
  typeDefs: /* GraphQL */ `
    type Mutation {
      publish(message: String!): String!
    }

    type Query {
      dummy: String!
    }

    type Subscription {
      messageFeed: String!
    }
  `,
  resolvers: {
    Query: {
      dummy: () => 'dummy',
    },
    Mutation: {
      publish: async (rootValue, { message }) => {
        await pubSub.publish('NEW_MESSAGE)', { message });

        return message;
      },
    },
    Subscription: {
      messageFeed: {
        // rootValue is same as object published using pubSub.publish
        resolve: rootValue => rootValue.message,
        subscribe: pubSub.subscribe('NEW_MESSAGE'),
      },
    },
  },
});

const eventProcessor = createDynamoDBEventProcessor({
  connectionManager,
  schema,
  subscriptionManager,
});
const wsHandler = createWsHandler({
  connectionManager,
  schema,
  subscriptionManager,
  // validationRules

  /* Lifecycle methods available from 0.9.0 */
  /* allows to provide custom parameters for operation execution */
  // onOperation?: (message: OperationRequest, params: { query, variables, operationName, context, schema }, connection: IConnection) => Promise<Object> | Object,

  /* executes on subscription stop operations, receives connection object and operation ID as arguments */
  // onOperationComplete?: (connection: IConnection, opId: string) => void,

  /* 
    executes on GQL_CONNECTION_INIT message, receives payload of said message and allows to reject connection when onConnect thorws error or returns false,
    write method return value to connection context which will be available during graphql resolver execution.
    If onConnect is not defined or returns true, we put everything in GQL_CONNECTION_INIT messagePayload to connection context.
  */
  // onConnect?: (messagePayload: { [key: string]: any }, connection: IConnection) => Promise<boolean | { [key: string]: any }> | boolean | { [key: string]: any },

  /* executes on $disconnect, receives connection object as argument */
  // onDisconnect?: (connection: IConnection) => void,

  // waitForInitialization (available from 0.11.0)
  // waitForInitialization: { retryCount?: number, timeout?: number },
});

// use these handlers from your lambda and map them to
// api gateway v2 and DynamoDB events table
module.exports.consumeWsEvent = wsHandler;
module.exports.consumeDynamoDBStream = eventProcessor;

Usage (Apollo client + subscriptions-transport-ws)

This library can be used with Apollo client without any problems thanks to AlpacaGoesCrazy's pull request #27.

yarn add apollo-client subscriptions-transport-ws
# or
npm install apollo-client subscriptions-transport-ws

Implement your simple client

import { WebSocketLink } from 'apollo-link-ws';
import { InMemoryCache } from 'apollo-cache-inmemory';
import { ApolloClient } from 'apollo-client';
import { SubscriptionClient } from 'subscriptions-transport-ws';

const wsClient = new SubscriptionClient(
  'ws://localhost:8000',
  { lazy: true, reconnect: true },
  null,
  [],
);
const link = new WebSocketLink(wsClient);
const client = new ApolloClient({
  cache: new InMemoryCache(),
  link,
});

// ...

Usage (Apollo client + aws-lambda-ws-link)

This library also have it's own client which is using a little bit different protocol than Apollo's.

yarn add aws-lambda-ws-link graphql
# or
npm install aws-lambda-ws-link graphql

Implement your simple client

import { Client, WebSocketLink } from 'aws-lambda-ws-link';
import { InMemoryCache } from 'apollo-cache-inmemory';
import { ApolloClient } from 'apollo-client';

const wsClient = new Client({
  uri: 'ws://localhost:8000',
});
const link = new WebSocketLink(wsClient);
const client = new ApolloClient({
  cache: new InMemoryCache(),
  link,
});

// ...

Examples

  • Chat App - React app
  • Chat Server
    • contains AWS Lambda that handles HTTP, WebSocket and DynamoDB streams
    • also includes serverless.yaml file for easy deployment

Contributing

Running tests locally:

yarn install
yarn test

This project uses TypeScript for static typing. Please add yourself to contributors according to all-contributors specification. You can use yarn all-contributors add {your-github-username} code,bug,....

Contributors

Michal Kvasničák
Michal Kvasničák

💬 💻 🎨 📖 💡 🤔 👀 ⚠️
AlpacaGoesCrazy
AlpacaGoesCrazy

💻 🐛 📖 ⚠️
Carlos Guerrero
Carlos Guerrero

💻 🐛
Samuel Marks
Samuel Marks

💻 🐛

This project follows the all-contributors specification. Contributions of any kind welcome!

License

MIT