pass pubsub into context in graphql subscription

185 views Asked by At

I'm struggling with this all day long, I want to implement subscriptions feature on my mongodb-express-graphql-nextjs project. I can't use context.pubsub in resolver. Am I missing something? please save me!

Problem

Cannot use pubsub in subscription context.

comment.gql.js

export const resolvers = {
  Subscription: {
    commentAdded: {
      subscribe: (_, args, context) => {
        console.log(context) // <---------- Undefined
        return context.pubsub.asyncIterator('NEW_COMMENT')  
        // An Error occurred. cannot read property 'pubsub' in 'context
      },
    },
  },
  Query: {
      ... other operations work well
  }, 
  Mutation: {
      ... other operations work well
  }
}

What I've done

Apollo Server

import express from 'express'
import { ApolloServer } from '@apollo/server'
import { expressMiddleware } from '@apollo/server/express4'
import { ApolloServerPluginDrainHttpServer } from '@apollo/server/plugin/drainHttpServer'
import { PubSub } from 'graphql-subscriptions'
import graphqlUploadExpress from 'graphql-upload/graphqlUploadExpress'
import cors from 'cors'
import cookieParser from 'cookie-parser'
import { json } from 'body-parser'
import http from 'http'
import schema from './controllers'
import { WebSocketServer } from 'ws'
import { useServer } from 'graphql-ws/lib/use/ws'

const startApolloServer = async () => {
  const pubsub = new PubSub()

  const app = express()
  const httpServer = http.createServer(app)
  // Create Web Socket
  const wsServer = new WebSocketServer({
    server: httpServer,
    path: '/graphql',
  })

  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(cookieParser())
  app.use(express.static('public'))
  app.use(
    graphqlUploadExpress({
      maxFileSize: 1000000000, // 100 MB
      maxFiles: 10,
    })
  )

  // Specify the path where we'd like to mount our server
  app.use(
    '/graphql',
    cors(),
    json(),
    expressMiddleware(apolloServer, {
      context: async ({ req }) => ({ req, pubsub }), // <--- Here I put pubsub into context
    })
  )
  app.set('trust proxy', true)

  app.use('*', (req, res, next) => {
    next()
  })

  await new Promise((resolve) => httpServer.listen({ port: 4000 }, resolve))
  console.log(` Server ready at http://localhost:4000/graphql`)
}

const run = async () => {
  try {
    await startApolloServer()
  } catch (err) {
    console.log(err)
  }
}

run()

Apollo Client (Next.js)

import { useMemo } from 'react'
import { split, InMemoryCache, ApolloClient } from '@apollo/client'
import { setContext } from '@apollo/client/link/context'
import { createUploadLink } from 'apollo-upload-client'
import getConfig from 'next/config'
import possibleTypes from './possibleTypes.json'
import fetch from 'isomorphic-fetch'
import { getToken } from './utils/auth'
import { GraphQLWsLink } from '@apollo/client/link/subscriptions'
import { createClient } from 'graphql-ws'
import { getMainDefinition } from '@apollo/client/utilities'

const { publicRuntimeConfig } = getConfig()
const { NODE_ENV } = publicRuntimeConfig

let apolloClient

const authLink = setContext((_, { headers }) => {
  // get the authentication token from cookie if it exists
  const token = getToken()
  // return the headers to the context so httpLink can read them
  return {
    headers: {
      ...headers,
      authorization: token ? `Bearer ${token}` : '',
    },
  }
})

const wsLink =
  typeof window !== 'undefined'
    ? new GraphQLWsLink(
        createClient({
          url: 'ws://localhost:4000/graphql',
        })
      )
    : null

const httpLink = createUploadLink({
  uri: NODE_ENV === 'development' ? 'http://localhost:4000/graphql' : 'https://coos.kr/graphql',
  headers: { 'apollo-require-preflight': 'true' },
  credentials: 'same-origin',
  fetch,
})

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

function createApolloClient(initialState, ctx) {
  return new ApolloClient({
    ssrMode: typeof window === 'undefined',
    link: splitLink,
    cache: new InMemoryCache({
      possibleTypes,
    }).restore(initialState),
    credentials: 'include',
  })
}

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

  if (initialState) {
    _apolloClient.cache.restore(initialState)
  }

  if (typeof window === 'undefined') return _apolloClient
  apolloClient = apolloClient ?? _apolloClient

  return apolloClient
}

export function useApollo(initialState) {
  const store = useMemo(() => initializeApollo(initialState), [initialState])
  return store
}

And below is my schema shape

import GraphQLUpload from 'graphql-upload/GraphQLUpload'
import { makeExecutableSchema } from '@graphql-tools/schema'
import { merge } from 'lodash'
import { typeDef as User, resolvers as userResolvers } from './user.gql'
import { typeDef as Post, resolvers as postResolvers } from './post.gql'
import { typeDef as Comment, resolvers as commentResolvers } from './comment.gql'

const rootTypeDef = `
  type Query {
    _empty: String
  }
  type Mutation {
    _empty: String
  }
  type Subscription {
    _empty: String
  }
  scalar Upload
`

export default makeExecutableSchema({
  typeDefs: [
    rootTypeDef,
    User,
    Post,
    Comment,
  ],
  resolvers: merge(
    { Upload: GraphQLUpload },
    userResolvers,
    postResolvers,
    commentResolvers,
  ),
})
0

There are 0 answers