Hi, I’ve been trying to get GraphQL subscriptions working in my Meteor/Apollo stack for some time and I’ve finally managed to get it working, but I don’t really understand why it’s working and if I’ve done anything wrong. Here is the full server & client code.
Server code:
import { ApolloServer } from "apollo-server"
import { makeExecutableSchema } from "graphql-tools"
import { getUser } from "meteor/apollo"
import merge from "lodash/merge"
const typeDefs = [/* Bunch of Schemas*/]
const resolvers = merge(/* Bunch of Resolvers */)
const schema = makeExecutableSchema({ typeDefs, resolvers })
const context = async ({ req }) => ({ user: await getUser(req?.headers.authorization) })
const server = new ApolloServer({
schema,
context,
subscriptions : {
path: '/subscriptions',
onConnect: (connectionParams, webSocket, context) => {
console.log('Client connected')
},
onDisconnect: (webSocket, context) => {
console.log('Client disconnected')
}
}
})
server.listen({ port: 4000 }).then(({ url, subscriptionsUrl }) => {
console.log(`🚀 Server ready at ${url}`)
console.log(`🚀 Subscriptions ready at ${subscriptionsUrl}`)
})
Client code:
import React from "react"
import { Meteor } from "meteor/meteor"
import { render } from "react-dom"
import { BrowserRouter } from "react-router-dom"
import { ApolloClient, ApolloProvider, HttpLink, split, InMemoryCache } from "@apollo/client"
import { ApolloLink, from } from "apollo-link"
import { getMainDefinition } from '@apollo/client/utilities';
import { WebSocketLink } from "@apollo/client/link/ws"
import { offsetLimitPagination } from "@apollo/client/utilities"
import App from "./some_path/App"
const httpLink = new HttpLink({
uri: "http://XXX.XXX.XX.XX:4000/graphql"
})
const authLink = new ApolloLink((operation, forward) => {
const token = Accounts._storedLoginToken()
operation.setContext(() => ({
headers: {
authorization: token
}
}))
return forward(operation)
})
const wsLink = new WebSocketLink({
uri: "ws://XXX.XXX.XX.XX:4000/subscriptions",
options: {
reconnect: true
}
})
const splitLink = split(
({ query }) => {
const definition = getMainDefinition(query)
return (
definition.kind === 'OperationDefinition' &&
definition.operation === 'subscription'
)
},
wsLink,
httpLink
)
const cache = new InMemoryCache({
typePolicies: {
Query: {
fields: {
getFeed: offsetLimitPagination()
}
}
}
})
const client = new ApolloClient({
link: from([authLink, splitLink]),
cache
})
const ApolloApp = () => (
<BrowserRouter>
<ApolloProvider client={client}>
<App />
</ApolloProvider>
</BrowserRouter>
)
Meteor.startup(() => {
render(<ApolloApp />, document.getElementById("app"))
})
I’ll now step through the code about the bits I don’t understand/have concerns:
const context = async ({ req }) => ({ user: await getUser(req?.headers.authorization) })
- I had to do
req?instead ofreqbecause I was getting an error in GraphiQL that'headers'was not defined whenever I tried to run a subscription. Doesn’t feel like the right way to do this though.
For the next question I need to show my previous server code. Note that ApolloServer here is imported from apollo-server-express instead of apollo-server as per my new code:
const server = new ApolloServer({
schema, // Same as per above
context // Same as per above except without the ?
})
server.applyMiddleware({
app: WebApp.connectHandlers,
path: "/graphql",
bodyParserConfig: {
limit: "50mb"
}
})
WebApp.connectHandlers.use("/graphql", (req, res) => {
if (req.method === "GET") {
res.end()
}
})
-
Here I had to apply middleware and pass in Meteor’s
WebApp.connectHandlers. The key difference is that my graphql endpoint here was on port 3000 but now it’s on port 4000 (I still access my app on port 3000). Since I removed this bit, I’m not sure what exactly changed apart from the port. If I understand this code correctly, this is telling Meteor that any requests which hit the/graphqlendpoint should not allow access anywhere within the app since this is the endpoint for GraphiQL (and queries, etc). -
If this indeed works without any problem, does this mean I don’t need to use swydo:ddp-apollo to solve this? One limitation of this package is that you lose access to GraphiQL. I personally can’t access GraphiQL in the Apollo Dev Tools either because the screen is completely blank (it’s a known issue with the browser extension - has been raised in the repository)
EDIT: I observed that I have two WebSocket connections open, 1 on port 3000 for Meteor through SockJS and 1 on 4000/subscriptions for my GraphQL subscriptions. On my client, after Meteor.startup, I disconnected the DDP with Meteor.disconnect so now I only have my GraphQL subscriptions WebSocket open. Of course this means I can’t have Hot Code Push working. I don’t use the DDP for anything other than HCP.
- Are there any ramifications for this?
So far all my queries/mutations/subscriptions and routing, etc all work perfectly fine as per before, but I would like to understand more precisely what I’ve actually changed, as I’ve had to do quite a bit of trial and error without completely understanding what I’m doing. Appreciate any help in explaining this, thank you!
