ApolloServer 4: Client Can’t Connect to Subscriptions?

  Kiến thức lập trình

I’ve got my new ApolloServer 4 client and server setup. It’s working for queries and mutations, but not for subscriptions. Subscriptions aren’t connecting at all. It’s sending context to queries and mutations, but my context function isn’t being triggered at all for subscriptions. I think it may need a split link of some kind but I’ve been studying this and trying different splitLinks for 3-4 weeks and haven’t solved it yet.

What am I missing?

ApolloServer Setup

import {ApolloServer} from "@apollo/server";
import {expressMiddleware} from "@apollo/server/express4";
import {ApolloServerPluginDrainHttpServer} from "@apollo/server/plugin/drainHttpServer";
import express from "express";
import http from "http";
import cors from "cors";
import {PubSub, withFilter} from "graphql-subscriptions-continued";
import {WebSocketServer} from "ws";
import {useServer} from "graphql-ws/lib/use/ws";
import {makeExecutableSchema} from "@graphql-tools/schema";
import {MongodbPubSub} from 'graphql-mongodb-subscriptions';
import {getUserIdByLoginToken} from "./utils_server/meteor-apollo-utils";
import typeDefs from "./api/schema";
import {resolvers} from "./api/resolvers";
import mongoose from "mongoose";
import bodyParser from "body-parser";

const MONGODB_URI = `mongodb://localhost:3001/meteor`;

const connectToDb = async () => {
    await mongoose.connect(MONGODB_URI);
};

await connectToDb();
console.log('???? Connected to database successfully');

const mongodbpubsub = new MongodbPubSub({
    connectionDb: mongoose.connections[0].db
});
// Create the schema, which will be used separately by ApolloServer and
// the WebSocket server.
const schema = makeExecutableSchema({typeDefs, resolvers});

// https://www.youtube.com/watch?v=AcZ5dcYMwA4
const app = express();
const httpServer = http.createServer(app);
const wsServer = new WebSocketServer({
// port: 4000,
    path: "/graphql",
    server: httpServer,
});

const serverCleanUp = useServer({schema}, wsServer);
const apolloServer = new ApolloServer({
    schema,
    plugins: [
        ApolloServerPluginDrainHttpServer({httpServer}),
        {
            async serverWillStart() {
                return {
                    async drainServer() {
                        await serverCleanUp.dispose();
                    }
                }
            }
        }
    ],
});

await apolloServer.start();


app.use(
    "/graphql",
    cors({
        origin: "http://localhost:3000",
        credentials: true,
    }),
    express.json(),
    expressMiddleware(apolloServer, {
        context: async (ctx, msg, args) => {
            // You can define your own function for setting a dynamic context
            // or provide a static value
            // return getDynamicContext(ctx, msg, args);
            let token = ctx.req.headers['token']
            let user = null;
            let userId = null;
            try {
                if ((!!token) && (token !== "null")) {
                    [user, userId] = await getUserIdByLoginToken(token);
                }
            } catch (error) {
                console.log('context: ', error)
            }

            let clientIp = '';
            try {
                clientIp = ctx.req.headers['x-forwarded-for'] || ctx.req.connection.remoteAddress;
            } catch {
                console.log("Couldn't get clientIp in ctx.req")
            }

            return {
                user: user,
                userId: userId,
                clientIp: clientIp,
                pubsub: mongodbpubsub
            };
        }
    })
);

await new Promise((resolve) => httpServer.listen({port: 4000}, resolve));

const PORT = 4000;
console.log(`Server is now running on http://localhost:${PORT}/graphql`);



// https://github.com/mjwheatley/graphql-mongodb-subscriptions/issues/43
// https://github.com/mjwheatley/apollo-graphql-mongodb/blob/main/src/index.ts
// https://www.apollographql.com/docs/apollo-server/migration/#migrate-from-apollo-server-express
// …in the section following the text:
//     The context function's syntax is similar for the expressMiddleware function:
// https://www.apollographql.com/docs/apollo-server/data/subscriptions
// https://www.apollographql.com/docs/apollo-server/data/subscriptions/#production-pubsub-libraries

ApolloClient setup

import { ApolloClient, HttpLink, InMemoryCache, ApolloLink, ApolloProvider } from "@apollo/client";

// Create an HttpLink pointing to your GraphQL endpoint
const httpLink = new HttpLink({ uri: 'http://localhost:4000/graphql' });

// Middleware to add custom headers
const customHeadersMiddleware = new ApolloLink((operation, forward) => {
    // Define your custom headers
    const customHeaders = {
        "token": localStorage.getItem("Meteor.loginToken")
    };

    // Use operation.setContext to add the custom headers to the request
    operation.setContext(({ headers }) => ({
        headers: {
            ...headers,
            ...customHeaders,
        },
    }));

    return forward(operation);
});

// Combine the middleware with the HttpLink
const apolloClient = new ApolloClient({
    link: customHeadersMiddleware.concat(httpLink),
    cache: new InMemoryCache(),
});

export {apolloClient};

LEAVE A COMMENT