javascriptgraphqlapolloapollo-servergraphql-subscriptions

Why is my graphql Context returning an empty object


I have built graphql apollo (v4) server by following the docs. I just made the subscription and pubsub work.

However, when I was trying to work on authentication, I realized that my graphql server does not provide context to my resolvers as expected.

Here is my code.

// index.ts
import { ApolloServer } from '@apollo/server';
import { expressMiddleware } from '@apollo/server/express4';
import { ApolloServerPluginDrainHttpServer } from '@apollo/server/plugin/drainHttpServer';
import express from 'express';
import { createServer } from 'http';
import { makeExecutableSchema } from '@graphql-tools/schema';
import { WebSocketServer } from 'ws';
import { useServer } from 'graphql-ws/lib/use/ws';
import bodyParser from 'body-parser';
import cors from 'cors';
import { PubSub } from 'graphql-subscriptions';
import { typeDefs, resolvers } from './graphql'

const PORT = 4000;

// Create schema, which will be used separately by ApolloServer and
// the WebSocket server.
const schema = makeExecutableSchema({ typeDefs, resolvers });

// Create an Express app and HTTP server; we will attach the WebSocket
// server and the ApolloServer to this HTTP server.
const app = express();
const httpServer = createServer(app);

// Set up WebSocket server.
const wsServer = new WebSocketServer({
  server: httpServer,
  path: '/graphql',
});

const pubsub = new PubSub();

const getSubscriptionContext = async (ctx: any): Promise<any> => {
  ctx;
  console.log('@@ ctx', ctx);

  // ctx is the graphql-ws Context where connectionParams live
  if (ctx.connectionParams && ctx.connectionParams.session) {
    const { session } = ctx.connectionParams;
    return { session, pubsub };
  }
  // Otherwise let our resolvers know we don't have a current user
  return { session: null, pubsub };
};

const serverCleanup = useServer(
  {
    schema,
    context: (ctx: any) => getSubscriptionContext(ctx)
  },
  wsServer
);

// Set up ApolloServer.
const server = new ApolloServer({
  schema,
  plugins: [
    // Proper shutdown for the HTTP server.
    ApolloServerPluginDrainHttpServer({ httpServer }),

    // Proper shutdown for the WebSocket server.
    {
      async serverWillStart() {
        return {
          async drainServer() {
            await serverCleanup.dispose();
          },
        };
      },
    },
  ],
});

async function init() {
  await server.start();
  app.use('/graphql', cors<cors.CorsRequest>(), bodyParser.json(), expressMiddleware(server));

  // Now that our HTTP server is fully set up, actually listen.
  httpServer.listen(PORT, () => {
    console.log(`🚀 Query endpoint ready at http://localhost:${PORT}/graphql`);
    console.log(`🚀 Subscription endpoint ready at ws://localhost:${PORT}/graphql`);
  });
};

init();

For some reason, the getSubscriptionContext does not seem to be running at all. When I run query in the browser, I do not see console.log from this function at all. I do not think the server is skipping this block.

Some of the posts have context passed to ApolloServer constructor, but the ApolloServer from @apollo/server (v4) no longer accepts a context parameter.

In the resolver I have:

resolvers: {
    Query: {
      users: (_: any, __: any, context: any) => {
        console.log('@@ context', context);
        return 123;
      }
    }
  } 

this console.log returns an empty object.

@@ context {}

Here's a sample query and mutation:

const user = {
  typeDefs: `
    extend type Query {
      hello: Int
    }

    extend type Mutation {
      hi(name: String): Int
    }
  `,
  resolvers: {
    Query: {
      hello: (_: any, __: any, context: any) => {
        console.log('@@ context', context);
        return 123;
      }
    },
    Mutation: {
      hi: (_: any, args: any, context: any) => {
        console.log('@@', context, args);
        return 123;
      }
    }
  } 
} 

A context is essential for authentication, how can I gain access to it?


Solution

    1. create a context function near the top of your code
    const context = (ctx) => ctx;
    
    1. Add this function as an argument to your expressMiddleware call:
    app.use(
      '/graphql', 
      cors<cors.CorsRequest>(), 
      bodyParser.json(),
      expressMiddleware(server, { context })
    );
    

    docs