Websockets are not working in production environment

In my production environment, I’m not getting real-time updates on the client side. The backend and GraphQL API are both working fine. (Since my previous issue isn’t resolved yet, I don’t want to be advised on to check if subscriptions are working fine in my sandbox playground)

import { configureStore } from "@reduxjs/toolkit"
import authReducer, { reset } from "../features/auth/authSlice"
import crowdReducer, { resetCrowdState } from "../features/user/crowdSlice"
import onboardReducer, { resetBillingState } from "../features/auth/billingSlice"
import { ApolloClient, InMemoryCache, ApolloProvider, gql, createHttpLink, split } from "@apollo/client"
import { GraphQLWsLink } from "@apollo/client/link/subscriptions"
import { setContext } from "@apollo/client/link/context"
import { Provider } from "react-redux"
import { getMainDefinition } from "@apollo/client/utilities"
import { createClient } from "graphql-ws"
import cloud_linkReducer, { resetCloudState } from "../features/user/cloudSlice"

const isDevelopment = process.env.VITE_NODE_ENV == "development"
const serverUrl = isDevelopment ? `${process.env.VITE_LOCALHOST}:${process.env.VITE_PORT}` : process.env.CLIENT_URL
const wsUrl = isDevelopment ? `ws://localhost:${process.env.VITE_PORT}/api/v1/graphql` : `wss://www.abc.com/api/v1/graphql`
export const store = configureStore({
  reducer: {
    auth: authReducer,
    crowd: crowdReducer,
    onboard: onboardReducer,
    cloud: cloud_linkReducer,
  },
  devTools: isDevelopment,
})

export const resetAllState = () => (dispatch) => {
  dispatch(reset())
  dispatch(resetBillingState())
  dispatch(resetCrowdState())
  dispatch(resetCloudState())
}

const httpLink = new createHttpLink({
  uri: `${serverUrl}/api/v1/graphql`,
})

const authLink = setContext((_, { headers }) => {
  const client = localStorage.getItem("client")
  let token
  try {
    token = client ? JSON.parse(client).token : null
  } catch (error) {
    console.error("Failed to parse client token from local storage", error)
  }
  return {
    headers: {
      ...headers,
      authorization: token ? `Bearer ${token}` : "",
    },
  }
})

// Create a WebSocket client
const wsClient = createClient({
  url: wsUrl,
  connectionParams: () => ({
    token: JSON.parse(localStorage.getItem("client"))?.token,
  }),
})

// Create a WebSocket link and use split for proper link selection
const wsLink = new GraphQLWsLink(wsClient)
const link = split(
  ({ query }) => {
    const definition = getMainDefinition(query)
    return definition.kind === "OperationDefinition" && definition.operation === "subscription"
  },
  wsLink,
  authLink.concat(httpLink)
)

const client = new ApolloClient({
  link,
  cache: new InMemoryCache(),
})

// Wrap your app with ApolloProvider and Redux Provider
export const AppProviders = ({ children }) => {
  return (
    <ApolloProvider client={client}>
      <Provider store={store}>{children}</Provider>
    </ApolloProvider>
  )
}

Hey @S4nfs a variety of things could be happening here so would you be able to provide me with some more information?

  • How are you triggering the subscription? Is this useSubscription, subscribeToMore, or calling observableQuery.subscribe(...) directly?
  • Is your backend compatible with the graphql-ws protocol? (in other words, does it use graphql-ws on the server as well)
  • Are you able to verify that your server is actually sending events? Is it just that the client is not re-rendering properly?
  • What client version are you using?
  • How does redux fit into this?

I think this is likely a typo on your end, but I also noticed this line:

const httpLink = new createHttpLink({
  uri: `${serverUrl}/api/v1/graphql`,
})

That new keyword is not needed here unless you use the HttpLink constructor (i.e. new HttpLink(...)).

Yes, i am using useSubscription from my react client and both my ends are using graphql-ws, plus i already implemented a resolver and all my subscriptions are working in my development environment.

My versions are:
Backend: @apollo/server": “4.9.5” , graphql-ws: “5.14.2”,
Frontend: @apollo/client": “3.8.7”, graphql-ws: “5.14.2”,

Just forget about redux it’s just clearing the combined state, it wasn’t here before.

Sandbox throwing error on subscriptions:
Firefox_Screenshot_2023-11-28T08-05-13.613Z

I guess my client side configuration is correct, here is what i am doing in my backend side:

const httpServer = createServer(app)

// create websocket server
const wsServer = new WebSocketServer({
  server: httpServer,
  path: "/api/v1/graphql",
})
wsServer.on("error", (error) => {
  console.error(`WebSocket server error:  ${error}`.black.bgRed)
})
const serverCleanup = useServer(
  {
    schema,
    onConnect: async (ctx) => {
      console.log("Connecting!")
      console.log(ctx.connectionParams)
    },
    onDisconnect(ctx, code, reason) {
      console.log("Disconnected!")
    },
  },
  wsServer
)

const apolloServer = new ApolloServer({
  schema,
  introspection: isDevelopment,
  plugins: [
    // Proper shutdown for the HTTP server.
    ApolloServerPluginDrainHttpServer({ httpServer }),
    // Proper shutdown for the WebSocket server.
    {
      async serverWillStart() {
        return {
          async drainServer() {
            await serverCleanup.dispose()
          },
        }
      },
    },
  ],
  playground: {
    settings: {
      "request.credentials": "include",
    },
  },
})

// routes
app.use("/api/", require("./routes/config.route"))
app.use("/api/v1/auth", require("./routes/auth.route"))

app.use("/api/v1/profile", require("./routes/profile.route"))
app.use("/api/v1/admin", passport.authenticate("jwt", { session: false }), ensureAdmin, require("./routes/admin.route"))

// 404 handler
// app.use((req, res, next) => {
//   next(createHttpError.NotFound())
// })

app.use((error, req, res, next) => {
  error.status = error.status || 500
  res.status(error.status).json({
    message: {
      error: [error.message],
    },
  })
})

const start = async () => {
  await connectDB(`${process.env.VITE_MONGO_URI}`)
    .then((conn) => {
      console.log(`MongoDB Connected: ${conn.connection.host}`.black.bgCyan)
    })
    .catch((err) => {
      console.log(`MongoDB Error: ${err}`.black.bgRed)
    })

  await apolloServer.start()

  app.use(
    "/api/v1/graphql",
    passport.authenticate("jwt", { session: false }),
    expressMiddleware(apolloServer, {
      context: async ({ req }) => {
        console.log("Is authenticated: ", req.isAuthenticated())
        return req.isAuthenticated() ? { user: req.user } : null
      },
    })
  )
  isDevelopment && app.use("/api/v1/sandbox", expressMiddleware(apolloServer))
  httpServer.listen(PORT, () => console.log(`Listening on port: ${process.env.VITE_PORT} (in ${process.env.VITE_NODE_ENV})`.black.bgGreen))
}

function ensureAdmin(req, res, next) {
  if (req.user.role === roles.admin || req.user.role === roles.enterprise) {
    next()
  } else {
    req.flash("warning", "Unauthorised user")
    return res.status(401).json({
      success: 0,
      message: req.flash(),
    })
  }
}

app.use(errorHandler)
// start the server
start()

Nothing stands out at a glance to me as this all seems setup correctly. Without being able to run the code, its a bit difficult for me to determine exactly what might be happening. From a glance, it seems to be unable to connect to your server at all.

I’m not sure if it helps, but our Spotify Showcase used to do a direct connection to our local server using websockets with a very similar setup. This was working correctly with an almost identical setup. Perhaps there is something you can glean from that code that you can compare against yours to see if something stands out?

Browse the file tree with these commits specifically. We updated the showcase to use Apollo’s GraphOS which uses multipart HTTP requests for websockets, so the latest code on main doesn’t include this setup anymore. I’m hoping something in here helps!

It would help me out at some point. By the way, this is a nice project. I have one question - is it deployed on a domain? I would appreciate it if you could share the url. Cause I am not having any problems on my localhost, but the real test is in production.

Yes! You can find it here: https://comforting-syrniki-15960f.netlify.app/

We’re looking to secure a domain name for this eventually, but for now you can find it here. Note that the deployed version uses Apollo’s GraphOS with multipart subscriptions, so websockets aren’t used here. Hope this app helps you!

Don’t know if it helps, but make sure your production server has Websockets enabled.

Some providers have the option turned off by default.
For example, on Azure Web Apps, you need to manually switch it on for Websockets to work.

@Kheang_Hok_Chin I am using bare Metal aws instance (ubuntu 20.04 LTS image) and the project is deployed on nginx server .

I am not familiar with AWS but I think in NGINX you have to configure it to support websockets if its not already configured to work.

If you test your production build locally and it works using websockets, it would point out that its a configuration issue on the production server.

@Kheang_Hok_Chin, thank you, it’s working. Everything was fine; I just had to configure the NGINX server to use WebSockets. But still i am unable to use subscriptions in my apollo sandbox (related to my previous issue: Not getting Context value in my resolvers!)

1 Like