npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

graphql-lambda

v1.1.0

Published

Ready-to-use Node.js GraphQL server for AWS Lambda (with subscriptions)

Downloads

211

Readme

npm version

This is a AWS Lambda integration of GraphQL Server with Subscriptions support.

Heavily inspired on aws-lambda-graphql module.

Also based on Apollo Server (a community-maintained open-source GraphQL server that works with many Node.js HTTP server frameworks).

npm install graphql-lambda

Working Examples

Ready-to-deploy (serverless deploy) examples:

Deploying with AWS Serverless Framework

GraphQL SDL Example:

Define GraphQL Schemas

Following the same GraphQL SDL setup as in graphql-lambda-sdl-example, define your Schemas and subscriptions in a file called schema.ts

import { pubSub, withFilter } from "../lambda";

type MessageType = 'greeting' | 'test';

type Message = {
  text: string;
  type: MessageType;
};

type SendMessageArgs = {
  text: string;
  type: MessageType;
};

export const typeDefs = /* GraphQL */ `
  enum MessageType {
    greeting
    test
  }
  type Message {
    id: ID!
    text: String!
    type: MessageType!
  }
  type Mutation {
    sendMessage(text: String!, type: MessageType = greeting): Message!
  }
  type Query {
    serverTime: Float!
  }
  type Subscription {
    messageFeed(type: MessageType): Message!
  }
`;

export const resolvers = {
  Mutation: {
    async sendMessage(ctx: any, { text, type }: SendMessageArgs) {
      const payload: Message = { text, type };

      await pubSub.publish('NEW_MESSAGE', payload);

      return payload;
    },
  },
  Query: {
    serverTime: () => Date.now(),
  },
  Subscription: {
    messageFeed: {
      resolve: (rootValue: Message) => {
        // root value is the payload from sendMessage mutation
        return rootValue;
      },
      subscribe: withFilter(
        pubSub.subscribe('NEW_MESSAGE'),
        (rootValue: Message, args: { type: null | MessageType }) => {
          // this can be async too :)
          if (args.type == null) {
            return true;
          }

          return args.type === rootValue.type;
        },
      ),
    },
  },
};

Initialize all components for Subscriptions to work with AWS API Gateway V2 Websockets:

In a file named lambda.ts place the following code:

import { APIGatewayProxyEvent } from "aws-lambda";
import {
  PubSub,
  SubscriptionManager,
  ApiGatewayConnectionManager,
  ISubscriptionEvent,
  IEventStore,
  APIGatewayWebSocketEvent,
} from "graphql-lambda";

import { SQS } from "aws-sdk";

export class SQSQueue implements IEventStore {
  public events: ISubscriptionEvent[];
  private sqs: SQS;

  constructor() {
    this.sqs = new SQS();
  }

  publish = async (event: ISubscriptionEvent | APIGatewayProxyEvent | APIGatewayWebSocketEvent): Promise<void> => {
    var params = {
      QueueUrl: process.env.sqsfifo,
      MessageGroupId: "0",
      MessageBody: JSON.stringify(event),
    };
    await this.sqs.sendMessage(params).promise();
  };
}

export const eventStore = new SQSQueue();
export const pubSub = new PubSub({ eventStore });
export const subscriptionManager = new SubscriptionManager({
  subscriptionManagerStorage: new Map(), // Replace this with any persistence layer you prefer, Redis, MySQL, etc.
});
export const connectionManager = new ApiGatewayConnectionManager({
  connectionManagerStorage: new Map(), // Replace this with any persistence layer you prefer, Redis, MySQL, etc.
});
export const eventProcessor = new EventProcessor()
export * from "graphql-lambda";

For production environments the new Map() should be replaced with a proper data storage like MySQL, Redis, DynamoDB, etc.

Server Creation

Provide the schema (type definitions and resolvers) to the GraphQL Server, together with the subscriptionsManager, connectionManager and eventProcessor defined in lambda.ts.

Place the following code in a file named graphql.ts:

import {
  Server,
  subscriptionManager,
  connectionManager,
  EventProcessor,
  APIGatewayWebSocketEvent,
  eventStore,
  eventProcessor,
} from "./lambda";

import { typeDefs, resolvers } from "./graphql/schema";

const server = new Server({
  typeDefs,  // (schema-first approach)
  resolvers, // (schema-first approach)
  // schema, // (code-first approach)
  connectionManager,
  eventProcessor,
  subscriptionManager,

  onError: (err) => {
    console.log(err);
  },
  playground: {
    endpoint: `/${process.env.STAGE}/graphql`,
    subscriptionEndpoint: process.env.lambdaSubscriptionEndpoint,
  },
});

export const handleHttp = server.createHttpHandler();
export const handleWebSocket = server.createWebSocketHandler(); // Required for subscriptions websockets
export const eventHandler = server.createEventHandler(); // Required for subscription events

Optionally, a schema parameter can be provided to the server instead of typeDefs and resolvers.

Separate subscriptions events into separate function through SQS (for memory persistence)

To keep this example simple and make it work without any kind of Database, we will make all graphql subscriptions events to be handled by a separated function, so we can just store everything in memory, and make sure only one function handles all the subscription-related events by piping all events through a AWS SQS FIFO Queue:

In a file named handlers.ts, place the following code:

import { handleHttp, handleWebSocket, eventHandler } from "./graphql"

export async function handleGraphql(
  event: APIGatewayEvent | APIGatewayWebSocketEvent,
  context
) {
  if (
    (event as APIGatewayWebSocketEvent).requestContext != null &&
    (event as APIGatewayWebSocketEvent).requestContext.routeKey != null
  ) {
    await eventStore.publish(event);

    let result = {
      body: "",
      headers: event.headers?.["Sec-WebSocket-Protocol"]?.includes("graphql-ws")
        ? {
            "Sec-WebSocket-Protocol": "graphql-ws",
          }
        : undefined,
      statusCode: 200,
    };

    return result;
  } else if (
    (event as APIGatewayEvent).requestContext != null &&
    (event as APIGatewayEvent).requestContext.path != null
  ) {
    return handleHttp(event as APIGatewayEvent, context);
  } else {
    throw new Error("Invalid event");
  }
}

export async function handleGraphqlSubscriptions(event: SQSEvent, context) {
  if ((event as SQSEvent).Records != null) {
    for (const record of (event as SQSEvent).Records) {
      const ev = JSON.parse(record.body);
      if (
        (ev as APIGatewayWebSocketEvent).requestContext != null &&
        (ev as APIGatewayWebSocketEvent).requestContext.routeKey != null
      ) {
        await handleWebSocket(ev as APIGatewayWebSocketEvent, context);
      } else {
        await eventHandler(ev, context);
      }
    }
  } else {
    throw new Error("Invalid event");
  }
}

Finally, this serverless project requires some custom resources.

In a file named serverless.ts (replacing the usual serverless.yml file), place the following code:

import type { AWS } from "@serverless/typescript";

const serverlessConfiguration: AWS = {
  service: "graphql-lambda-example",
  plugins: ["serverless-webpack"],
  provider: {
    apiGateway: {
      shouldStartNameWithService: true,
    },
    name: "aws",
    runtime: "nodejs12.x",
    region: "us-east-1",
    memorySize: 512,
    iam: {
      role: {
        statements: [
          {
            Effect: "Allow",
            Action: ["lambda:InvokeFunction"],
            Resource: "*",
          },
          {
            Effect: "Allow",
            Action: ["sqs:*"],
            Resource: "*",
          },
        ],
      },
    },
    environment: {
      STAGE: "${opt:stage, self:provider.stage}",
      sqsfifo: {
        "Fn::Join": [
          "",
          [
            "https://sqs.",
            { Ref: "AWS::Region" },
            ".amazonaws.com/",
            { Ref: "AWS::AccountId" },
            "/${self:custom.eventSQSFifo}",
          ],
        ],
      },
      lambdaSubscriptionEndpoint: {
        "Fn::Join": [
          "",
          [
            "wss://",
            {
              Ref: "WebsocketsApi",
            },
            ".execute-api.",
            {
              Ref: "AWS::Region",
            },
            ".",
            {
              Ref: "AWS::URLSuffix",
            },
            "/${self:provider.stage}",
          ],
        ],
      },
    },
  },
  functions: {
    main: {
      handler: "src/handlers.handleGraphql",
      events: [
        {
          http: {
            path: "/graphql",
            method: "ANY",
          },
        },
        {
          websocket: {
            route: "$connect",
          },
        },
        {
          websocket: {
            route: "$default",
          },
        },
        {
          websocket: {
            route: "$disconnect",
          },
        },
      ],
    },
    queue: {
      handler: "src/handlers.handleGraphqlSubscriptions",
      events: [
        {
          sqs: {
            arn: {
              "Fn::GetAtt": ["sqsfifo", "Arn"],
            },
          },
        },
      ],
    },
  },
  custom: {
    stage: "${opt:stage, self:provider.stage}",
    region: "${opt:region, self:provider.region}",
    eventSQSFifo: "${self:service}-sqs-fifo-${self:custom.stage}.fifo",
    webpack: {
      webpackConfig: "./webpack.config.js",
      includeModules: true,
    },
  },
  resources: {
    Resources: {
      sqsfifo: {
        Properties: {
          QueueName: "${self:custom.eventSQSFifo}",
          FifoQueue: true,
          ContentBasedDeduplication: true,
        },
        Type: "AWS::SQS::Queue",
      },
    },
  },
};

module.exports = serverlessConfiguration;

Deploy!

Deploy to AWS Lambda and start testing!

serverless deploy

Example Playground Screenshots

Query:

Screenshot 2021-03-20 at 4 06 47 PM

Subscription:

Screenshot 2021-03-20 at 4 06 31 PM

Mutation:

Screenshot 2021-03-20 at 4 06 19 PM