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 ofreq
because 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/graphql
endpoint 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!