Apollo GraphQL: GraphQLWsLink (Subscriptions) Troubles. Cannot get WebSocket implementation to work w/ Next.js
Asked Answered
G

2

8

So I have a GraphQL server that I wrote in Go, following this tutorial pretty closely. I have my front-end written as a Next.js application, and I am currently trying to create a client to connect to my server and even following the subscription docs to the T, I cannot seem to get it to work. How is it that the examples provided do not include a webSocketImpl?

If I don't provide a webSocketImpl, I get this:

Error: WebSocket implementation missing; on Node you can `import WebSocket from 'ws';` and pass `webSocketImpl: WebSocket` to `createClient`

So, naturally, I import { WebSocket } from "ws"; , and have:

const wsLink = new GraphQLWsLink(
    createClient({
        webSocketImpl: WebSocket,
        url: "ws://localhost:8080/subscriptions",
    })
);

Where I then get:

error - ./node_modules/node-gyp-build/index.js:1:0
Module not found: Can't resolve 'fs'

Here is the full code, basically all I need is to create a ApolloClient and export it for use in my React code.

import { ApolloClient, HttpLink, InMemoryCache, split } from "@apollo/client";
import { GraphQLWsLink } from "@apollo/client/link/subscriptions";
import { createClient } from "graphql-ws";
import { getMainDefinition } from "@apollo/client/utilities";
import { WebSocket } from "ws";

const wsLink = new GraphQLWsLink(
    createClient({
        webSocketImpl: WebSocket,
        url: "ws://localhost:8080/subscriptions",
    })
);

const httpLink = new HttpLink({
    uri: `http://localhost:8080/query`,
});

const link = split(
    ({ query }) => {
        const def = getMainDefinition(query);
        return (
            def.kind === "OperationDefinition" && def.operation === "subscription"
        );
    },
    wsLink,
    httpLink
);

export const Client = new ApolloClient({
    link,
    cache: new InMemoryCache(),
});

Am I totally missing something here? Is there not a default WebSocket implementation in my installation? Obviously the "ws" implementation isn't cutting it, probably because fs is not available in-browser?

Glarum answered 4/5, 2022 at 17:11 Comment(1)
would you mind sharing your apollo-server code please?Archway
G
25

A major thing I left off here: I was using Next.js. The reason this was occurring was due to SSR. Basically, we need to only generate the WebSocket link if we are in the browser by using `typeof window !== 'undefined'. This is my updated code:

import { ApolloClient, HttpLink, InMemoryCache, split } from "@apollo/client";
import { GraphQLWsLink } from "@apollo/client/link/subscriptions";
import { createClient } from "graphql-ws";
import { getMainDefinition } from "@apollo/client/utilities";

const wsLink =
    typeof window !== "undefined"
        ? new GraphQLWsLink(
                createClient({
                    url: "ws://localhost:8080/subscriptions",
                })
          )
        : null;

const httpLink = new HttpLink({
    uri: `http://localhost:8080/query`,
});

const link =
    typeof window !== "undefined" && wsLink != null
        ? split(
                ({ query }) => {
                    const def = getMainDefinition(query);
                    return (
                        def.kind === "OperationDefinition" &&
                        def.operation === "subscription"
                    );
                },
                wsLink,
                httpLink
          )
        : httpLink;

export const client = new ApolloClient({
    link,
    cache: new InMemoryCache(),
});
Glarum answered 4/5, 2022 at 17:35 Comment(1)
Good stuff mate. Was battling with this for a while.Zealot
H
0

I found a way how it`s work with GraphQL-yoga It's client :

// import { createServer } from "@graphql-yoga/node";
import { makeExecutableSchema } from "@graphql-tools/schema";
import typeDefs from "@/server/graphql/typeDef/schema.graphql";
import resolvers from "@/server/graphql/resolvers";
import dbInit from "@/lib/dbInit";
import JWT from "jsonwebtoken";
import Cors from "micro-cors";

// const pubsub = new PubSub();
const schema = makeExecutableSchema({
  typeDefs,
  resolvers,
});

import {
  createServer,
  createPubSub,
  GraphQLYogaError,
} from "@graphql-yoga/node";
import { useResponseCache } from "@envelop/response-cache";
import { WebSocketServer } from "ws"; // yarn add ws
// import ws from 'ws'; yarn add ws@7
// const WebSocketServer = ws.Server;
import { useServer } from "graphql-ws/lib/use/ws";

const pubSub = createPubSub();

const server = createServer({
  cors: {
    credentials: "same-origin",
    origin: ["http://localhost:3000"], // your frontend url.
  },

  plugins: [
    useResponseCache({
      includeExtensionMetadata: true,
    }),
  ],
  context: async (ctx) => {
    let wsServer = null;
    wsServer = ctx.res.socket.server.ws ||= new WebSocketServer({
      port: 4000,
      path: "/api/graphql",
    });
    wsServer &&= useServer({ schema }, wsServer);

    const db = await dbInit();
    let { token, customerId, customerExpire } = ctx.req.cookies;
    // 1. Find optional visitor id
    let id = null;
    if (token) {
      try {
        let obj = JWT.verify(token, "MY_SECRET");
        id = obj.id;
      } catch (err) {
        console.error("error on apollo server", err); // expired token, invalid token
        // TODO try apollo-link-error on the client
        throw new AuthenticationError(
          "Authentication token is invalid, please log in"
        );
      }
    }

    return {
      ...ctx,
      userId: id,
      customerId,
      pubSub,
    };
  },
  schema,

});

export default server;

And client

import { useMemo } from "react";
import {
  ApolloClient,
  InMemoryCache,
  split,
  HttpLink,
  createHttpLink,
} from "@apollo/client";
import merge from "deepmerge";
import { getMainDefinition } from "apollo-utilities";
import { GraphQLWsLink } from "@apollo/client/link/subscriptions";
import { createClient } from "graphql-ws";

// const link = process.env.SERVER_LINK;
let apolloClient;
//create websocket link
const wsLink =
  typeof window !== "undefined"
    ? new GraphQLWsLink(
        createClient({
          url: "ws://localhost:4000/api/graphql",

          on: {
            connected: () => console.log("connected client"),
            closed: () => console.log("closed"),
          },
        })
      )
    : null;


//create http link
const httplink = new HttpLink({
  uri: "http://localhost:3000/api/graphql",
  credentials: "same-origin",
});

//Split the link based on graphql operation
const link =
  typeof window !== "undefined"
    ? split(
        //only create the split in the browser
        // split based on operation type
        ({ query }) => {
          const { kind, operation } = getMainDefinition(query);
          return kind === "OperationDefinition" && operation === "subscription";
        },
        wsLink,
        httplink
      )
    : httplink;

//create apollo client
function createApolloClient() {
  return new ApolloClient({
    ssrMode: typeof window === "undefined",
    link: link,
    cache: new InMemoryCache(),
  });
}

export function initializeApollo(initialState = null) {
  const _apolloClient = apolloClient ?? createApolloClient();

  if (initialState) {
    // Get existing cache, loaded during client side data fetching
    const existingCache = _apolloClient.extract();

    // Merge the existing cache into data passed from getStaticProps/getServerSideProps
    const data = merge(initialState, existingCache);

    // Restore the cache with the merged data
    _apolloClient.cache.restore(data);
  }

  if (typeof window === "undefined") return _apolloClient;

  if (!apolloClient) apolloClient = _apolloClient;

  return _apolloClient;
}

export function useApollo(initialState) {
  const store = useMemo(() => initializeApollo(initialState), [initialState]);
  return store;
}
Heavenly answered 14/6, 2022 at 22:11 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.