How to do graphql and graphql subscriptions with svelte - graphql

To do graphql queries and mutations ive had success with both fetch and svelte-apollo (see https://github.com/timhall/svelte-apollo)
I like the fech approach for its simplicity.
Svelte-apollo features subscriptions and I will try to get it to work.
But are there alternatives?
How do you consume graphql subscriptions with svelte?

Heres my solution, using Apollo:
import { ApolloClient } from 'apollo-client';
import { InMemoryCache } from 'apollo-cache-inmemory';
import { HttpLink } from 'apollo-link-http';
import { WebSocketLink } from 'apollo-link-ws';
import { split } from 'apollo-link';
import { getMainDefinition } from 'apollo-utilities';
const httpLink = new HttpLink({
uri: 'http://localhost:3000/graphql'
});
const wsLink = new WebSocketLink({
uri: `ws://localhost:3000/subscriptions`,
options: {
reconnect: true
}
});
const link = split(
// split based on operation type
({ query }) => {
const definition = getMainDefinition(query);
return (
definition.kind === 'OperationDefinition' &&
definition.operation === 'subscription'
);
},
wsLink,
httpLink,
);
const client = new ApolloClient({
link,
cache: new InMemoryCache()
});
After all that, you have your client set up. Next,
import gql from 'graphql-tag';
client.subscribe({
query: gql`subscription { whatever }`
}).subscribe(result => console.log(result.data);

I'm using urql's svelte bindings. The documentation also shows how to use the bindings with subscriptions.

Related

Apollo GraphQL react client: subscription

I was trying to make a little demo with GraphQL subscriptions and GraphQL Apollo client.
I already have my GraphQL API, but when I try to use Apollo client, it looks like it doesn't complete the websocket subscribe step:
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import { ApolloClient, InMemoryCache, ApolloProvider, gql, useQuery } from '#apollo/client';
import { split, HttpLink } from '#apollo/client';
import { getMainDefinition } from '#apollo/client/utilities';
import { GraphQLWsLink } from '#apollo/client/link/subscriptions';
import { createClient } from 'graphql-ws';
import { useSubscription } from '#apollo/react-hooks'
import reportWebVitals from './reportWebVitals';
const httpLink = new HttpLink({
uri: 'https://mygraphql.api'
});
const wsLink = new GraphQLWsLink(createClient({
url: 'wss://mygraphql.api',
options: {
reconnect: true
}
}));
const splitLink = split(
({ query }) => {
const definition = getMainDefinition(query);
return (
definition.kind === 'OperationDefinition' &&
definition.operation === 'subscription'
);
},
wsLink,
httpLink,
);
const client = new ApolloClient({
link: splitLink,
cache: new InMemoryCache(),
fetchOptions: {
mode: 'no-cors',
}
});
const FAMILIES_SUBSCRIPTION = gql`
subscription{
onFamilyCreated {
id
name
}
}
`;
function LastFamily() {
const { loading, error, data } = useSubscription(FAMILIES_SUBSCRIPTION, {
variables: { },
onData: data => console.log('new data', data)
});
if (loading) return <div>Loading...</div>;
if (error) return <div>Error!</div>;
console.log(data);
const family = data.onFamilyCreated[0];
return (
<div>
<h1>{family.name}</h1>
</div>
);
}
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
(<ApolloProvider client={client}>
<div>
<LastFamily />
</div>
</ApolloProvider>));
reportWebVitals();
According to graphql-transport-ws, to accomplish a success call, it should call connection_init and subscribe message. But when I open Dev Tools, it only sends "connection_init"
I'm expecting this output:
What step should I add to accomplish a successful call using graphql-transport-ws?
P.s. I'm not a React Developer, just be kind.
The solutions I'm putting up are based on #apollo/server v.4, with expressMiddleware and mongodb/mongoose on the backend and subscribeToMore with updateQuery on the client-side instead of useSubscription hook. In light of my observations, I believe there may be some issues with your backend code that require refactoring. The transport library graphql-transport-ws has been deprecated and advise to use graphql-ws. The following setup also applies as of 12.2022.
Subscription on the backend
Install the following dependencies.
$ npm i #apollo/server #graphql-tools/schema graphql-subscriptions graphql-ws ws cors body-parser mongoose graphql express
Set up the db models, I will refer to mongodb using mongoose and it might look like this one e.g.
import mongoose from 'mongoose'
const Schema = mongoose.Schema
const model = mongoose.model
const FamilySchema = new Schema({
name: {
type: String,
unique: true, //optional
trim: true,
}
})
FamilySchema.virtual('id').get(function () {
return this._id.toHexString()
})
FamilySchema.set('toJSON', {
virtuals: true,
transform: (document, retObj) => {
delete retObj.__v
},
})
const FamilyModel = model('FamilyModel', FamilySchema)
export default FamilyModel
Setup schema types & resolvers; it might look like this one e.g.
// typeDefs.js
const typeDefs = `#graphql
type Family {
id: ID!
name: String!
}
type Query {
families: [Family]!
family(familyId: ID!): Family!
}
type Mutation {
createFamily(name: String): Family
}
type Subscription {
familyCreated: Family
}
`
// resolvers.js
import { PubSub } from 'graphql-subscriptions'
import mongoose from 'mongoose'
import { GraphQLError } from 'graphql'
import FamilyModel from '../models/Family.js'
const pubsub = new PubSub()
const Family = FamilyModel
const resolvers = {
Query: {
families: async () => {
try {
const families = await Family.find({})
return families
} catch (error) {
console.error(error.message)
}
},
family: async (parent, args) => {
const family = await Family.findById(args.familyId)
return family
},
Mutation: {
createFamily: async (_, args) => {
const family = new Family({ ...args })
try {
const savedFamily = await family.save()
const createdFamily = {
id: savedFamily.id,
name: savedFamily.name
}
// resolvers for backend family subscription with object iterator FAMILY_ADDED
pubsub.publish('FAMILY_CREATED', { familyCreated: createdFamily })
return family
} catch (error) {
console.error(error.message)
}
}
},
Subscription: {
familyCreated: {
subscribe: () => pubsub.asyncIterator('FAMILY_CREATED'),
}
},
Family: {
id: async (parent, args, contextValue, info) => {
return parent.id
},
name: async (parent) => {
return parent.name
}
}
}
export default resolvers
At the main entry server file (e.g. index.js) the code might look like this one e.g.
import dotenv from 'dotenv'
import { ApolloServer } from '#apollo/server'
import { expressMiddleware } from '#apollo/server/express4'
import { ApolloServerPluginDrainHttpServer } from '#apollo/server/plugin/drainHttpServer'
import { makeExecutableSchema } from '#graphql-tools/schema'
import { WebSocketServer } from 'ws'
import { useServer } from 'graphql-ws/lib/use/ws'
import express from 'express'
import http from 'http'
import cors from 'cors'
import bodyParser from 'body-parser'
import typeDefs from './schema/tpeDefs.js'
import resolvers from './schema/resolvers.js'
import mongoose from 'mongoose'
dotenv.config()
...
mongoose.set('strictQuery', false)
let db_uri
if (process.env.NODE_ENV === 'development') {
db_uri = process.env.MONGO_DEV
}
mongoose.connect(db_uri).then(
() => {
console.log('Database connected')
},
(err) => {
console.log(err)
}
)
const startGraphQLServer = async () => {
const app = express()
const httpServer = http.createServer(app)
const schema = makeExecutableSchema({ typeDefs, resolvers })
const wsServer = new WebSocketServer({
server: httpServer,
path: '/',
})
const serverCleanup = useServer({ schema }, wsServer)
const server = new ApolloServer({
schema,
plugins: [
ApolloServerPluginDrainHttpServer({ httpServer }),
{
async serverWillStart() {
return {
async drainServer() {
await serverCleanup.dispose()
},
}
},
},
],
})
await server.start()
app.use(
'/',
cors(),
bodyParser.json(),
expressMiddleware(server)
)
const PORT = 4000
httpServer.listen(PORT, () =>
console.log(`Server is now running on http://localhost:${PORT}`)
)
}
startGraphQLServer()
Subscription on the CRA frontend
Install the following dependencies.
$ npm i #apollo/client graphql graphql-ws
General connection setup e.g.
// src/client.js
import { ApolloClient, HttpLink, InMemoryCache, split } from '#apollo/client'
import { getMainDefinition } from '#apollo/client/utilities'
import { defaultOptions } from './graphql/defaultOptions'
import { GraphQLWsLink } from '#apollo/client/link/subscriptions'
import { createClient } from 'graphql-ws'
...
const baseUri = process.env.REACT_APP_BASE_URI // for the client
const wsBaseUri = process.env.REACT_APP_WS_BASE_URI // for the backend as websocket
const httpLink = new HttpLink({
uri: baseUri,
})
const wsLink = new GraphQLWsLink(
createClient({
url: wsBaseUri
})
)
const splitLink = split(
({ query }) => {
const definition = getMainDefinition(query)
return (
definition.kind === 'OperationDefinition' &&
definition.operation === 'subscription'
)
},
wsLink,
httpLink
)
const client = new ApolloClient({
cache: new InMemoryCache(),
link: splitLink,
})
export default client
// src/index.js
import React from 'react'
import ReactDOM from 'react-dom/client'
import client from './client'
import { ApolloProvider } from '#apollo/client'
import App from './App'
const root = ReactDOM.createRoot(document.getElementById('root'))
root.render(
<React.StrictMode>
<ApolloProvider client={client}>
<App />
</ApolloProvider>
</React.StrictMode>
)
Define the operations types for the client: queries, mutation & subscription e.g.
// src/graphql.js
import { gql } from '#apollo/client'
// Queries
export const FAMILIES = gql`
query Families {
families {
id
name
}
}
`
export const FAMILY = gql`
query Family($familyId: ID) {
family {
id
name
}
}
`
// Mutation
export const CREATE_FAMILY = gql`
mutation createFamily($name: String!) {
createFamily(name: $name) {
id
name
}
}
`
// Subscription
export const FAMILY_SUBSCRIPTION = gql`
subscription {
familyCreated {
id
name
}
}
Components, it might look like this one e.g.
Apollo's useQuery hook provides us with access to a function called subscribeToMore. This function can be destructured and used to act on new data that comes in via subscription. This has the result of rendering our app real-time.
The subscribeToMore function utilizes a single object as an argument. This object requires configuration to listen for and respond to subscriptions.
At the very least, we must pass a subscription document to the document key in this object. This is a GraphQL document in which we define our subscription.
We can a updateQuery field that can be used to update the cache, similar to how we would do in a mutation.
// src/components/CreateFamilyForm.js
import { useMutation } from '#apollo/client'
import { CREATE_FAMILY, FAMILIES } from '../graphql'
...
const [createFamily, { error, loading, data }] = useMutation(CREATE_FAMILY, {
refetchQueries: [{ query: FAMILIES }], // be sure to refetchQueries after mutation
})
...
// src/components/FamilyList.js
import React, { useEffect, useState } from 'react'
import { useQuery } from '#apollo/client'
import { Families, FAMILY_SUBSCRIPTION } from '../graphql'
const { cloneDeep, orderBy } = pkg
...
export const FamilyList = () => {
const [families, setFamilies] = useState([])
const { loading, error, data, subscribeToMore } = useQuery(Families)
...
useEffect(() => {
if (data?.families) {
setFamilies(cloneDeep(data?.families)) // if you're using lodash but it can be also setFamilies(data?.families)
}
}, [data?.families])
useEffect(() => {
subscribeToMore({
document: FAMILY_SUBSCRIPTION,
updateQuery: (prev, { subscriptionData }) => {
if (!subscriptionData.data) return prev
const newFamily = subscriptionData.data.familyCreated
if (!prev.families.find((family) => family.id === newFamily.id)) {
return Object.assign({}, prev.families, {
families: [...prev.families, newFamily],
})
} else {
return prev
}
},
})
}, [subscribeToMore])
const sorted = orderBy(families, ['names'], ['desc']) // optional; order/sort the list
...
console.log(sorted)
// map the sorted on the return statement
return(...)
END. Hard-coding some of the default resolvers are useful for ensuring that the value that you expect will returned while avoiding the return of null values. Perhaps not in every case, but for fields that refer to other models or schema.
Happy coding!

Apollo GraphQL: GraphQLWsLink (Subscriptions) Troubles. Cannot get WebSocket implementation to work w/ Next.js

So I have a GraphQL server that I wrote in Go, following this tutorial pretty closely. I have my front-end written as a Next.js application, and I am currently trying to create a client to connect to my server and even following the subscription docs to the T, I cannot seem to get it to work. How is it that the examples provided do not include a webSocketImpl?
If I don't provide a webSocketImpl, I get this:
Error: WebSocket implementation missing; on Node you can `import WebSocket from 'ws';` and pass `webSocketImpl: WebSocket` to `createClient`
So, naturally, I import { WebSocket } from "ws"; , and have:
const wsLink = new GraphQLWsLink(
createClient({
webSocketImpl: WebSocket,
url: "ws://localhost:8080/subscriptions",
})
);
Where I then get:
error - ./node_modules/node-gyp-build/index.js:1:0
Module not found: Can't resolve 'fs'
Here is the full code, basically all I need is to create a ApolloClient and export it for use in my React code.
import { ApolloClient, HttpLink, InMemoryCache, split } from "#apollo/client";
import { GraphQLWsLink } from "#apollo/client/link/subscriptions";
import { createClient } from "graphql-ws";
import { getMainDefinition } from "#apollo/client/utilities";
import { WebSocket } from "ws";
const wsLink = new GraphQLWsLink(
createClient({
webSocketImpl: WebSocket,
url: "ws://localhost:8080/subscriptions",
})
);
const httpLink = new HttpLink({
uri: `http://localhost:8080/query`,
});
const link = split(
({ query }) => {
const def = getMainDefinition(query);
return (
def.kind === "OperationDefinition" && def.operation === "subscription"
);
},
wsLink,
httpLink
);
export const Client = new ApolloClient({
link,
cache: new InMemoryCache(),
});
Am I totally missing something here? Is there not a default WebSocket implementation in my installation? Obviously the "ws" implementation isn't cutting it, probably because fs is not available in-browser?
A major thing I left off here: I was using Next.js. The reason this was occurring was due to SSR. Basically, we need to only generate the WebSocket link if we are in the browser by using `typeof window !== 'undefined'. This is my updated code:
import { ApolloClient, HttpLink, InMemoryCache, split } from "#apollo/client";
import { GraphQLWsLink } from "#apollo/client/link/subscriptions";
import { createClient } from "graphql-ws";
import { getMainDefinition } from "#apollo/client/utilities";
const wsLink =
typeof window !== "undefined"
? new GraphQLWsLink(
createClient({
url: "ws://localhost:8080/subscriptions",
})
)
: null;
const httpLink = new HttpLink({
uri: `http://localhost:8080/query`,
});
const link =
typeof window !== "undefined" && wsLink != null
? split(
({ query }) => {
const def = getMainDefinition(query);
return (
def.kind === "OperationDefinition" &&
def.operation === "subscription"
);
},
wsLink,
httpLink
)
: httpLink;
export const client = new ApolloClient({
link,
cache: new InMemoryCache(),
});
I found a way how it`s work with GraphQL-yoga
It's client :
// import { createServer } from "#graphql-yoga/node";
import { makeExecutableSchema } from "#graphql-tools/schema";
import typeDefs from "#/server/graphql/typeDef/schema.graphql";
import resolvers from "#/server/graphql/resolvers";
import dbInit from "#/lib/dbInit";
import JWT from "jsonwebtoken";
import Cors from "micro-cors";
// const pubsub = new PubSub();
const schema = makeExecutableSchema({
typeDefs,
resolvers,
});
import {
createServer,
createPubSub,
GraphQLYogaError,
} from "#graphql-yoga/node";
import { useResponseCache } from "#envelop/response-cache";
import { WebSocketServer } from "ws"; // yarn add ws
// import ws from 'ws'; yarn add ws#7
// const WebSocketServer = ws.Server;
import { useServer } from "graphql-ws/lib/use/ws";
const pubSub = createPubSub();
const server = createServer({
cors: {
credentials: "same-origin",
origin: ["http://localhost:3000"], // your frontend url.
},
plugins: [
useResponseCache({
includeExtensionMetadata: true,
}),
],
context: async (ctx) => {
let wsServer = null;
wsServer = ctx.res.socket.server.ws ||= new WebSocketServer({
port: 4000,
path: "/api/graphql",
});
wsServer &&= useServer({ schema }, wsServer);
const db = await dbInit();
let { token, customerId, customerExpire } = ctx.req.cookies;
// 1. Find optional visitor id
let id = null;
if (token) {
try {
let obj = JWT.verify(token, "MY_SECRET");
id = obj.id;
} catch (err) {
console.error("error on apollo server", err); // expired token, invalid token
// TODO try apollo-link-error on the client
throw new AuthenticationError(
"Authentication token is invalid, please log in"
);
}
}
return {
...ctx,
userId: id,
customerId,
pubSub,
};
},
schema,
});
export default server;
And client
import { useMemo } from "react";
import {
ApolloClient,
InMemoryCache,
split,
HttpLink,
createHttpLink,
} from "#apollo/client";
import merge from "deepmerge";
import { getMainDefinition } from "apollo-utilities";
import { GraphQLWsLink } from "#apollo/client/link/subscriptions";
import { createClient } from "graphql-ws";
// const link = process.env.SERVER_LINK;
let apolloClient;
//create websocket link
const wsLink =
typeof window !== "undefined"
? new GraphQLWsLink(
createClient({
url: "ws://localhost:4000/api/graphql",
on: {
connected: () => console.log("connected client"),
closed: () => console.log("closed"),
},
})
)
: null;
//create http link
const httplink = new HttpLink({
uri: "http://localhost:3000/api/graphql",
credentials: "same-origin",
});
//Split the link based on graphql operation
const link =
typeof window !== "undefined"
? split(
//only create the split in the browser
// split based on operation type
({ query }) => {
const { kind, operation } = getMainDefinition(query);
return kind === "OperationDefinition" && operation === "subscription";
},
wsLink,
httplink
)
: httplink;
//create apollo client
function createApolloClient() {
return new ApolloClient({
ssrMode: typeof window === "undefined",
link: link,
cache: new InMemoryCache(),
});
}
export function initializeApollo(initialState = null) {
const _apolloClient = apolloClient ?? createApolloClient();
if (initialState) {
// Get existing cache, loaded during client side data fetching
const existingCache = _apolloClient.extract();
// Merge the existing cache into data passed from getStaticProps/getServerSideProps
const data = merge(initialState, existingCache);
// Restore the cache with the merged data
_apolloClient.cache.restore(data);
}
if (typeof window === "undefined") return _apolloClient;
if (!apolloClient) apolloClient = _apolloClient;
return _apolloClient;
}
export function useApollo(initialState) {
const store = useMemo(() => initializeApollo(initialState), [initialState]);
return store;
}

Apollo Subscription with Gatsby Setup

below is my setup of Gatsby SSR with Apollo for those looking for a solution on how to set them up. Hope it helps those looking online for a solution on how to set up Gatsby with Apollo
Credits: [https://github.com/apollographql/subscriptions-transport-ws/issues/333]
I am not sure at the moment if my onError variable is placed correctly. I will look it up
Nonetheless, subscription with gatsby client-side routes is working fine with this setup
client.js // to be imported into ApolloProvider
import ApolloClient from 'apollo-client'
// import * as ws from 'ws'
// import { createHttpLink } from 'apollo-link-http'
import { split } from 'apollo-link'
// import { setContext } from 'apollo-link-context'
import { onError } from 'apollo-link-error'
import { HttpLink } from 'apollo-link-http'
import { WebSocketLink } from 'apollo-link-ws'
// import fetch from 'isomorphic-fetch' // Comment out this line results in an error ...
import 'isomorphic-fetch'
import { InMemoryCache } from 'apollo-cache-inmemory'
import { getMainDefinition } from 'apollo-utilities'
const HTTP_URI = `http://localhost:4000/graphql`
const WS_URI = `ws://localhost:4000/graphql`
const httplink = new HttpLink({
uri: HTTP_URI,
// credentials: 'same-origin',
// fetch,
})
const wsLink = process.browser
? new WebSocketLink({
// if you instantiate in the server, the error will be thrown
uri: WS_URI,
options: {
reconnect: true,
},
})
: null
const errorLink = onError(({ networkError, graphQLErrors }) => {
if (graphQLErrors) {
graphQLErrors.map(({ message, locations, path }) =>
console.log(
`[GraphQL error]: Message: ${message}, Location: ${locations}, Path: ${path}`
)
)
}
if (networkError) console.log(`[Network error]: ${networkError}`)
})
const link = process.browser
? split(
//only create the split in the browser
// split based on operation type
({ query }) => {
const { kind, operation } = getMainDefinition(query)
return kind === 'OperationDefinition' && operation === 'subscription'
},
wsLink,
httplink,
errorLink
)
: httplink
export const client = new ApolloClient({
link,
cache: new InMemoryCache(),
})
graphql/subscriptions.js
import gql from 'graphql-tag'
const BOOK_ADDED_SUBSCRIPTION = gql`
subscription {
bookAdded {
_id
title
author
}
}
`
export { BOOK_ADDED_SUBSCRIPTION }

Apollo 2.x graphIQL Can't Reach Subscription?

My Apollo queries and mutations are working perfectly. Now I'm adding a subscription and for some reason graphIql (please note the I in graphIql) can't trigger the subscription. A breakpoint in the subscription doesn't activate, and the data comes back to graphIql as null. There are no console log errors in graphIql.
Here's the relevant code:
TYPES
type Resolution {
_id: String!
name: String!
goals: [Goal]
completed: Boolean
}
type Query {
resolutions: [Resolution]
getResolutionViaId(resolutionId: String!): Resolution
}
type Mutation {
createResolution(name: String!): Resolution
}
type Subscription {
resolutionWasAdded: Resolution
}
RESOLVERS
Subscription: {
resolutionWasAdded: {
subscribe: () => pubsub.asyncIterator(RESOLUTION_SUBSCRIPTION_TOPIC)
}
}
QUERY ENTERED IN GRAPHIQL
subscription resolutionWasAdded {
resolutionWasAdded {
__typename
_id
name
completed
}
}
RESPONSE IN GRAPHIQL
{
"data": {
"resolutionWasAdded": null
}
}
What could I be missing?
UPDATE
I see I need to set up graphIql to be aware of my subscription endpoint. I've googled quite a bit but don't yet see how to do it with this client setup:
import React from "react";
import {Meteor} from "meteor/meteor";
import {render} from "react-dom";
import {ApolloProvider} from "react-apollo";
import {ApolloLink, from} from "apollo-link";
import {ApolloClient} from "apollo-client";
import {HttpLink} from "apollo-link-http";
import {InMemoryCache} from "apollo-cache-inmemory";
import {onError} from 'apollo-link-error';
import {split} from 'apollo-link';
import {WebSocketLink} from 'apollo-link-ws';
import {getMainDefinition} from 'apollo-utilities';
import {toIdValue} from 'apollo-utilities';
import App from "../../ui/App";
// Create an http link:
const httpLink = new HttpLink({
uri: Meteor.absoluteUrl("graphql"),
credentials: 'same-origin'
})
// Create a WebSocket link:
const wsLink = new WebSocketLink({
uri: `ws://localhost:3200/subscriptions`,
options: {
reconnect: true
}
});
// using the ability to split links, you can send data to each link
// depending on what kind of operation is being sent
const splitLink = split(
// split based on operation type
({query}) => {
const {kind, operation} = getMainDefinition(query);
return kind === 'OperationDefinition' && operation === 'subscription';
},
wsLink,
httpLink,
);
const authLink = new ApolloLink((operation, forward) => {
const token = Accounts._storedLoginToken();
operation.setContext(() => ({
headers: {
"meteor-login-token": token
}
}));
return forward(operation);
});
const client = new ApolloClient({
link: ApolloLink.from([
onError(({graphQLErrors, networkError}) => {
if (graphQLErrors)
graphQLErrors.map(({message, locations, path}) =>
console.log(
`[GraphQL error]: Message: ${message}, Location: ${locations}, Path: ${path}`,
),
);
if (networkError) console.log(`[Network error]: ${networkError}`);
}),
authLink,
splitLink,
]),
cache: new InMemoryCache({})
});
const ApolloApp = () => (
<ApolloProvider client={client}>
<App/>
</ApolloProvider>
);
Meteor.startup(() => {
render(<ApolloApp/>, document.getElementById("app"));
});
What is the correct way to modify this setup so as to tell graphIql about the subscription endpoint?

Adding Graph*I*QL Subscription Endpoint when Using WebSocketLinks from apollo-link-ws?

Here's my client-side code initializing ApolloClient:
import React from "react";
import {Meteor} from "meteor/meteor";
import {render} from "react-dom";
import {ApolloProvider} from "react-apollo";
import {ApolloLink, from} from "apollo-link";
import {ApolloClient} from "apollo-client";
import {HttpLink} from "apollo-link-http";
import {InMemoryCache} from "apollo-cache-inmemory";
import {onError} from 'apollo-link-error';
import {split} from 'apollo-link';
import {WebSocketLink} from 'apollo-link-ws'; //<======
import {getMainDefinition} from 'apollo-utilities';
import {toIdValue} from 'apollo-utilities';
import App from "../../ui/App";
// Create an http link:
const httpLink = new HttpLink({
uri: Meteor.absoluteUrl("graphql"),
credentials: 'same-origin'
})
// Create a WebSocket link:
const wsLink = new WebSocketLink({
uri: `ws://localhost:3200/subscriptions`,
options: {
reconnect: true
}
});
// using the ability to split links, you can send data to each link
// depending on what kind of operation is being sent
const splitLink = split(
// split based on operation type
({query}) => {
const {kind, operation} = getMainDefinition(query);
return kind === 'OperationDefinition' && operation === 'subscription';
},
wsLink,
httpLink,
);
const authLink = new ApolloLink((operation, forward) => {
const token = Accounts._storedLoginToken();
operation.setContext(() => ({
headers: {
"meteor-login-token": token
}
}));
return forward(operation);
});
const client = new ApolloClient({
link: ApolloLink.from([
onError(({graphQLErrors, networkError}) => {
if (graphQLErrors)
graphQLErrors.map(({message, locations, path}) =>
console.log(
`[GraphQL error]: Message: ${message}, Location: ${locations}, Path: ${path}`,
),
);
if (networkError) console.log(`[Network error]: ${networkError}`);
}),
authLink,
splitLink,
]),
cache: new InMemoryCache({})
});
const ApolloApp = () => (
<ApolloProvider client={client}>
<App/>
</ApolloProvider>
);
Meteor.startup(() => {
render(<ApolloApp/>, document.getElementById("app"));
});
This works perfectly on the client. And using it graphIql (please note the 'I') works fine for queries and mutations. But graphIql does not yet work for subscriptions, because I haven't yet told it the subscription endpoint.
Searching SO for answers doesn't seem to bring up any that apply when using WebSocketLinks rather than Express.
What's the correct way to tell graphIql the subscription endpoint when using WebSocketLinks?

Resources