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,
),
})