apollo-server icon indicating copy to clipboard operation
apollo-server copied to clipboard

Better support for websockets (subscriptions) for apollo-server-fastify

Open timukasr opened this issue 4 years ago • 6 comments

I'm trying to use implement GraphQL subscriptions using graphql-ws library and apollo-server-fastify. Logical middle step would be fastify-websocket library, but there is no good way to configure subscriptions end point to be same as main GraphQL server endpoint (e.g /graphql).

One simple solution would be to add optional wsHandler parameter to ApolloServer.createHandler method, split instance.route by method and add wsHandler to GET route, something like this:

instance.route({
  method: "GET",
  url: "/",
  preHandler,
  handler,
  wsHandler, // websocket handler
});
instance.route({
  method: "POST",
  url: "/",
  handler,
});

Usage:

import { execute, subscribe } from "graphql";
import { makeHandler } from "graphql-ws/lib/use/fastify-websocket";

const app = fastify();
const server = new ApolloServer({ schema });

await server.start();

app.register(
  server.createHandler({
    wsHandler: makeHandler({
      schema,
      execute,
      subscribe,
    }),
  }),
);

timukasr avatar Oct 25 '21 19:10 timukasr

Apollo Server 3 does not support subscriptions. I'm not sure it makes much sense to add partial support like this. Fastify doesn't provide any mechanism to let you call the AS handler on GET/POST and the WS handler on CONNECT?

glasser avatar Oct 26 '21 21:10 glasser

There should be other ways how to achieve it, e.g. subscriptions-transport-ws only needs server instance as input and works fine. Problem is that I have not found an easy way how to do something similar with apollo-server-fastify and fastify-express. Right now best solution for me seems to just fork apollo-server-fastify and make few simple changes rather than trying to take a deeper dive into fastify and websocket. Main aim of this Github issue is to check if others have similar issues and wether to try to fix it more generally.

timukasr avatar Oct 27 '21 17:10 timukasr

Trying to do the same.

jaydlawrence avatar Nov 17 '21 19:11 jaydlawrence

Hello, when will subscriptions be available for version 3? Do we have to go back to version 2?

lohnsonok avatar Jan 23 '22 20:01 lohnsonok

We answered this question in https://github.com/apollographql/apollo-server/issues/6020#issuecomment-1020543875

glasser avatar Jan 24 '22 21:01 glasser

Successful implementation of the 2022 bid.

import { env } from "process";
import { ApolloServer } from "apollo-server-fastify";
import { ApolloServerPluginDrainHttpServer } from "apollo-server-core";
import { ApolloServerPlugin } from "apollo-server-plugin-base";
import fastify, { FastifyInstance } from "fastify";
import { WebSocketServer } from "ws";
import { makeExecutableSchema } from "@graphql-tools/schema";
import { useServer } from "graphql-ws/lib/use/ws";

const fastifyAppClosePlugin = (app: FastifyInstance): ApolloServerPlugin => {
  return {
    async serverWillStart() {
      return {
        async drainServer() {
          await app.close();
        },
      };
    },
  };
};

export const startApolloServer = async (
  typeDefs: any,
  resolvers: any
): Promise<void> => {
  const app = fastify({
    https: {
      key: env.KEY ?? undefined,
      cert: env.CERT ?? undefined,
    },
  });

  const wsServer = new WebSocketServer({
    server: app.server,
    path: "/graphql",
  });

  const serverCleanup = useServer(
    { schema: makeExecutableSchema({ typeDefs, resolvers }) },
    wsServer
  );

  const server = new ApolloServer({
    typeDefs,
    resolvers,
    plugins: [
      fastifyAppClosePlugin(app as any),
      ApolloServerPluginDrainHttpServer({ httpServer: app.server as any }),
      {
        async serverWillStart() {
          return {
            async drainServer() {
              await serverCleanup.dispose();
            },
          };
        },
      },
    ],
  });

  await server.start();
  void app.register(import("@fastify/cookie"));
  void app.register(
    server.createHandler({
      cors: {
        origin: (_origin: any, cb: any) => {
          cb(null, true);
        },
        credentials: true,
      },
    })
  );
  await app.listen(env.PORT ?? 4000);
  console.log(
    `🚀 Server ready at https://localhost:${env.PORT ?? 4000}${
      server.graphqlPath
    }`
  );
};

momenthana avatar Jul 01 '22 11:07 momenthana