I have an Apollo GraphQL / Next.js application. After changing my graphql schema and navigating to the graphql playground at "http://localhost:3000/api/graphql", the old schema is still being referenced in the playground and in my application.
I've tried clearing node modules and running npm install, clearing cache, restarting everything, and I just can't wrap my head around why my schema is not updating. Am I missing some crucial schema-update step?
Here is my schema for Series and Publisher (note that a SeriesInput requires a Publisher, NOT a PublisherInput):
type Series {
_id: ID!
name: String
altID: String
publisher: Publisher!
comics: [Comic]
}
input SeriesInput {
_id: ID
name: String!
altID: String
publisher: Publisher!
comics: [Comic]
}
type Mutation {
addSeries(series: SeriesInput): Series
}
type Query {
series: [Series]
}
-------------------------
type Publisher {
_id: ID!
name: String
altID: String
series: [Series]
}
input PublisherInput {
_id: ID!
name: String!
altID: String
series: [Series]
}
type Mutation {
addPublisher(publisher: PublisherInput): Publisher
}
type Query {
publishers: [Publisher]
}
Here is the error message I am getting in GraphQL Playground which is due to the fact that the old series schema requires a PublisherInput type which has a mandatory field of "Name" which I am not passing.
Here is my graphql apollo server code where I am using mergeResolvers and mergeTypeDefs to merge all of the graphql files into a single schema:
import { ApolloServer } from "apollo-server-micro";
import { mergeResolvers, mergeTypeDefs } from "graphql-tools";
import connectDb from "../../lib/mongoose";
// Mutations and resolvers
import { comicsResolvers } from "../../api/comics/resolvers";
import { comicsMutations } from "../../api/comics/mutations";
import { seriesResolvers } from "../../api/series/resolvers";
import { seriesMutations } from "../../api/series/mutations";
import { publishersResolvers } from "../../api/publishers/resolvers";
import { publishersMutations } from "../../api/publishers/mutations";
// GraphQL Schema
import Publishers from "../../api/publishers/Publishers.graphql";
import Series from "../../api/series/Series.graphql";
import Comics from "../../api/comics/Comics.graphql";
// Merge type resolvers, mutations, and type definitions
const resolvers = mergeResolvers([
publishersMutations,
publishersResolvers,
seriesMutations,
seriesResolvers,
comicsMutations,
comicsResolvers,
]);
const typeDefs = mergeTypeDefs([Publishers, Series, Comics]);
// Create apollo server and connect db
const apolloServer = new ApolloServer({ typeDefs, resolvers });
export const config = {
api: {
bodyParser: false,
},
};
const server = apolloServer.createHandler({ path: "/api/graphql" });
export default connectDb(server);
Here is my apollo/next.js code which I used from Vercel's documentation:
* Code copied from Official Next.js documentation to work with Apollo.js
* https://github.com/vercel/next.js/blob/6e77c071c7285ebe9998b56dbc1c76aaf67b6d2f/examples/with-apollo/lib/apollo.js
*/
import React, { useMemo } from "react";
import Head from "next/head";
import { ApolloProvider } from "#apollo/react-hooks";
import { ApolloClient } from "apollo-client";
import { InMemoryCache } from "apollo-cache-inmemory";
import { HttpLink } from "apollo-link-http";
import fetch from "isomorphic-unfetch";
let apolloClient = null;
/**
* Creates and provides the apolloContext
* to a next.js PageTree. Use it by wrapping
* your PageComponent via HOC pattern.
* #param {Function|Class} PageComponent
* #param {Object} [config]
* #param {Boolean} [config.ssr=true]
*/
export function withApollo(PageComponent, { ssr = true } = {}) {
const WithApollo = ({ apolloClient, apolloState, ...pageProps }) => {
const client = useMemo(() => apolloClient || initApolloClient(apolloState), []);
return (
<ApolloProvider client={client}>
<PageComponent {...pageProps} />
</ApolloProvider>
);
};
// Set the correct displayName in development
if (process.env.NODE_ENV !== "production") {
const displayName = PageComponent.displayName || PageComponent.name || "Component";
if (displayName === "App") {
console.warn("This withApollo HOC only works with PageComponents.");
}
WithApollo.displayName = `withApollo(${displayName})`;
}
if (ssr || PageComponent.getInitialProps) {
WithApollo.getInitialProps = async (ctx) => {
const { AppTree } = ctx;
// Initialize ApolloClient, add it to the ctx object so
// we can use it in `PageComponent.getInitialProp`.
const apolloClient = (ctx.apolloClient = initApolloClient());
// Run wrapped getInitialProps methods
let pageProps = {};
if (PageComponent.getInitialProps) {
pageProps = await PageComponent.getInitialProps(ctx);
}
// Only on the server:
if (typeof window === "undefined") {
// When redirecting, the response is finished.
// No point in continuing to render
if (ctx.res && ctx.res.finished) {
return pageProps;
}
// Only if ssr is enabled
if (ssr) {
try {
// Run all GraphQL queries
const { getDataFromTree } = await import("#apollo/react-ssr");
await getDataFromTree(
<AppTree
pageProps={{
...pageProps,
apolloClient,
}}
/>
);
} catch (error) {
// Prevent Apollo Client GraphQL errors from crashing SSR.
// Handle them in components via the data.error prop:
// https://www.apollographql.com/docs/react/api/react-apollo.html#graphql-query-data-error
console.error("Error while running `getDataFromTree`", error);
}
// getDataFromTree does not call componentWillUnmount
// head side effect therefore need to be cleared manually
Head.rewind();
}
}
// Extract query data from the Apollo store
const apolloState = apolloClient.cache.extract();
return {
...pageProps,
apolloState,
};
};
}
return WithApollo;
}
/**
* Always creates a new apollo client on the server
* Creates or reuses apollo client in the browser.
* #param {Object} initialState
*/
function initApolloClient(initialState) {
// Make sure to create a new client for every server-side request so that data
// isn't shared between connections (which would be bad)
if (typeof window === "undefined") {
return createApolloClient(initialState);
}
// Reuse client on the client-side
if (!apolloClient) {
apolloClient = createApolloClient(initialState);
}
return apolloClient;
}
/**
* Creates and configures the ApolloClient
* #param {Object} [initialState={}]
*/
function createApolloClient(initialState = {}) {
// Check out https://github.com/zeit/next.js/pull/4611 if you want to use the AWSAppSyncClient
return new ApolloClient({
ssrMode: typeof window === "undefined", // Disables forceFetch on the server (so queries are only run once)
link: new HttpLink({
uri: "http://localhost:3000/api/graphql", // Server URL (must be absolute)
credentials: "same-origin", // Additional fetch() options like `credentials` or `headers`
fetch,
}),
cache: new InMemoryCache().restore(initialState),
});
}
I ran into the same problem and the reason that's happening is the way next js handles caching. Delete the .next folder then restart your server, that will solve the issue.
Well I spent almost 5 days trying to figure out what I did wrong or if Apollo server is caching the schema on production any to found the Apollo client origin is pointing to the wrong server.
Maybe you should check well as well.
Related
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!
I'm using API Routes on a Next.JS app with Apollo-Server, to create a GraphQL API wrapper around a REST endpoint. But I'm running into an issue if the project has a bathPath.
I've been following this example, and a video tutorial on youtube.
I've replicated my issue by using the repo from that tutorial. Upon running that code $ yarn dev, and navigating to http://localhost:3000/api/graphql the GraphQl playground shows up, and works as expected.
However if I add a basepath to the project then the graphQl playground still shows up fine at http://localhost:300/basepath/api/graphql but it gives me the error of "Server cannot be reached" and shows a 404 error in the network tab on the dev tools.
To add the base path I created a next.config.js and added
module.exports = {
basePath: '/basepath'
}
In pages/api/graphql.ts I updated the path from /api/graphql to /basepath/api/graphql
import { ApolloServer } from "apollo-server-micro";
import { schema } from "src/schema";
const server = new ApolloServer({ schema });
const handler = server.createHandler({ path: "/somewhere/api/graphql" });
export const config = {
api: {
bodyParser: false,
},
};
export default handler;
In src/apollo.ts I updated the HttpLink uri from /api/graphql to /basepath/api/graphql
import {
ApolloClient,
InMemoryCache,
NormalizedCacheObject,
} from "#apollo/client";
import { useMemo } from "react";
let apolloClient: ApolloClient<NormalizedCacheObject>;
function createIsomorphicLink() {
if (typeof window === "undefined") {
// server
const { SchemaLink } = require("#apollo/client/link/schema");
const { schema } = require("./schema");
return new SchemaLink({ schema });
} else {
// client
const { HttpLink } = require("#apollo/client/link/http");
return new HttpLink({ uri: "/bathpath/api/graphql" });
}
}
function createApolloClient() {
return new ApolloClient({
ssrMode: typeof window === "undefined",
link: createIsomorphicLink(),
cache: new InMemoryCache(),
});
}
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;
}
Any ideas why adding a basepath would break this setup? and what I'd need to do to fix it?
This is my first time posting on stack overflow, so I hope my description is good enough, please do ask if I've missed anything and thanks for your help in advance!
I hooked up a front end to a graphql server. Most if not all the mutations are protected while all the queries are not protected. I have an auth system in place where if you log in, you get an access/refresh token which all mutations are required to use. And they do which is great, backend receives the headers and everything!
HOWEVER. There is one query that needs at least the access token to distinguish the current user! BUT the backend does not receive the two headers! I thought that the middlewareLink I created would be for all queries/mutations but I'm wrong and couldn't find any additional resources to help me out.
So here's my setup
apollo-client.js
import { InMemoryCache } from "apollo-cache-inmemory"
import { persistCache } from "apollo-cache-persist"
import { ApolloLink } from "apollo-link"
import { HttpLink } from "apollo-link-http"
import { onError } from "apollo-link-error"
import { setContext } from "apollo-link-context"
if (process.browser) {
try {
persistCache({
cache,
storage: window.localStorage
})
} catch (error) {
console.error("Error restoring Apollo cache", error)
}
}
const httpLink = new HttpLink({
uri: process.env.GRAPHQL_URL || "http://localhost:4000/graphql"
})
const authMiddlewareLink = setContext(() => ({
headers: {
authorization: localStorage.getItem("apollo-token") || null,
"x-refresh-token": localStorage.getItem("refresh-token") || null
}
}))
const afterwareLink = new ApolloLink((operation, forward) =>
forward(operation).map(response => {
const context = operation.getContext()
const {
response: { headers }
} = context
if (headers) {
const token = headers.get("authorization")
const refreshToken = headers.get("x-refresh-token")
if (token) {
localStorage.setItem("apollo-token", token)
}
if (refreshToken) {
localStorage.setItem("refresh-token", refreshToken)
}
}
return response
})
)
const errorLink = onError(({ graphQLErrors, networkError }) => {
...
// really long error link code
...
})
let links = [errorLink, afterwareLink, httpLink]
if (process.browser) {
links = [errorLink, afterwareLink, authMiddlewareLink, httpLink]
}
const link = ApolloLink.from(links)
export default function() {
return {
cache,
defaultHttpLink: false,
link
}
}
Is there a way to target ALL mutations/queries with custom headers not just mutations? Or apply some headers to an individual query since I could probably put that as an app middleware?
edit: Haven't solved the SSR portion of this yet.. will re-edit with the answer once I have.
Using apollo and react, I want to update my local apollo state based on the result of a (remote) apollo query.
The query is of type [String!]!, my local state contains { name } of type String. I want to assign the local { name } to the first item returned by the query.
const App = compose(
graphql(gql`{ name #client }`, { name: 'localData' }),
graphql(gql`{ currencies { name } }`)
)(({ localData }) => <h1>{ localData.name }</h1>)
I don't want to use componentWillReceiveProps (mutating there the local state as soon as props come from the remote query), as it's bad practice.
I need to use local apollo state, not just the state of the component, as this value is going to be used by other components.
I tried using the props config of the graphql hoc, but it feels as bad as componentWillReceiveProps, and lead to an infinite loop.
Any idea?
Relevant versioning:
"apollo-boost": "^0.1.19",
"apollo-link-state": "^0.4.2",
"react-apollo": "^2.2.4",
Code
import React from 'react'
import ReactDOM from 'react-dom'
import ApolloClient from 'apollo-boost'
import gql from 'graphql-tag'
import { InMemoryCache } from 'apollo-cache-inmemory'
import { ApolloProvider, graphql, compose } from 'react-apollo'
import { ApolloLink } from 'apollo-link'
import { withClientState } from 'apollo-link-state'
import { HttpLink } from 'apollo-link-http'
const resolvers = {
Mutation: {
updateName: (ops, { name }, { cache }) => cache.writeData({ data: { name } })
}
}
const typedefs = gql`
type Query {
name: String
}
type Mutation {
updateName(name: String): String
}
`
const defaults = { name: 'defaultName' }
const cache = new InMemoryCache()
const stateLink = withClientState({
cache,
defaults,
resolvers
});
const apolloClient = new ApolloClient({
uri: '/store/api/graphql',
link: ApolloLink.from([stateLink, new HttpLink()]),
cache,
clientState: {
defaults,
resolvers,
typedefs
}
})
const App = compose(
graphql(gql`{ name #client }`, { name: 'localData' }),
graphql(gql`{ currencies { name id } }`)
)(({ localData }) => <h1>{ localData.name }</h1>)
ReactDOM.render(
<ApolloProvider client={ apolloClient }>
<App />
</ApolloProvider>,
document.getElementById('app')
)
I found a solution using the Query component instead of the graphql hoc. The Query component has the onCompleted property, where I can pass a function that updates the local state:
const UPDATE_NAME = gql`mutation ($name: String) {
updateName (name: $name) #client { name }
}`
const App = compose(
withApollo,
graphql(gql`{ name #client }`, { name: 'localData' })
)(({ client, localData }) => <Query
query={ gql`{ currencies { name id } }` }
onCompleted={ data =>
client.mutate({
mutation: UPDATE_NAME,
variables: { name: data.currencies[0].name }
})
}>
{
() => <h1>Current: { localData.name }</h1>
}
</Query>)
I find the Query component less elegant than the graphql hoc, but at least is a solution.
Recently they have deprecated the subscriptionManager. I would like to know how to setup resolvers, define subscribe and execute function.
You will need to upgrade to Apollo 2.0. I have recently done a write-up on how to use Apollo 2.0 since the official docs have not yet been updated.
In short, you have to use apollo-link now on the client and execute and subscribe from the graphql package now get passed directly to SubscriptionServer instead.
You will first need the right packages with the right versions:
npm install --save apollo-client#beta apollo-cache-inmemory#beta apollo-link#0.7.0 apollo-link-http#0.7.0 apollo-link-ws#0.5.0 graphql-subscriptions subscriptions-transport-ws apollo-server-express express graphql graphql-tools body-parser
If you're running Meteor, you might also need:
meteor add apollo swydo:blaze-apollo swydo:graphql webapp
Now for the code, the following was made in Meteor, but it can easily adapt to other server types, like Express. You can also download a working example app.
On the client:
import { ApolloClient } from 'apollo-client';
import { ApolloLink } from 'apollo-link';
import { HttpLink } from 'apollo-link-http';
import WebSocketLink from 'apollo-link-ws';
import Cache from 'apollo-cache-inmemory';
import { getOperationAST } from 'graphql';
const httpUri = 'http://localhost:3000/graphql';
const wsUri = 'ws://localhost:3000/subscriptions';
const link = ApolloLink.split(
operation => {
const operationAST = getOperationAST(operation.query, operation.operationName);
return !!operationAST && operationAST.operation === 'subscription';
},
new WebSocketLink({
uri: wsUri,
options: {
reconnect: true, //auto-reconnect
// // carry login state (should use secure websockets (wss) when using this)
// connectionParams: {
// authToken: localStorage.getItem("Meteor.loginToken")
// }
}
}),
new HttpLink({ uri: httpUri })
);
const cache = new Cache(window.__APOLLO_STATE);
const client = new ApolloClient({
link,
cache
});
On the server:
import { WebApp } from 'meteor/webapp'; // Meteor-specific
import { execute, subscribe } from 'graphql';
import { SubscriptionServer } from 'subscriptions-transport-ws';
import { createApolloServer, addCurrentUserToContext } from 'meteor/apollo'; // specific to Meteor, but you can always check out the Express implementation
import { makeExecutableSchema } from 'graphql-tools';
import resolvers from './resolvers'; // your custom resolvers
import typeDefs from './schema.graphql'; // your custom schema
// make schema executable
const schema = makeExecutableSchema({
typeDefs,
resolvers
});
// any additional context you use for your resolvers, if any
const context = {};
// start a graphql server with Express handling a possible Meteor current user
// if you're not using Meteor, check out https://github.com/apollographql/apollo-server for instructions on how to create a server in pure Node
createApolloServer({
schema,
context
}, {
// // enable access to GraphQL API cross-domain (requires NPM "cors" package)
// configServer: expressServer => expressServer.use(cors())
});
// create subscription server
// non-Meteor implementation here: https://github.com/apollographql/subscriptions-transport-ws
new SubscriptionServer({
schema,
execute,
subscribe,
// // on connect subscription lifecycle event
// onConnect: async (connectionParams, webSocket) => {
// // if a meteor login token is passed to the connection params from the client,
// // add the current user to the subscription context
// const subscriptionContext = connectionParams.authToken
// ? await addCurrentUserToContext(context, connectionParams.authToken)
// : context;
// return subscriptionContext;
// }
}, {
server: WebApp.httpServer,
path: '/subscriptions'
});
resolvers.js
import { withFilter } from 'graphql-subscriptions'; // will narrow down the changes subscriptions listen to
import { PubSub } from 'graphql-subscriptions';
import { People } from '../imports/api/collections'; // Meteor-specific for doing database queries
const pubsub = new PubSub();
const resolvers = {
Query: {
person(obj, args, context) {
const person = People.findOne(args.id);
if (person) {
// Mongo stores id as _id, but our GraphQL API calls for id, so make it conform to the API
person.id = person._id;
delete person._id;
}
return person;
}
},
Mutation: {
updatePerson(obj, args, context) {
// You'll probably want to validate the args first in production, and possibly check user credentials using context
People.update({ _id: args.id }, { $set: { name: args.name, eyeColor: args.eyeColor, occupation: args.occupation } });
pubsub.publish("personUpdated", { personUpdated: args }); // trigger a change to all subscriptions to this person
// Note: You must publish the object with the subscription name nested in the object!
// See: https://github.com/apollographql/graphql-subscriptions/issues/51
return args;
}
},
Subscription: {
personUpdated: {
// See: https://github.com/apollographql/graphql-subscriptions#channels-mapping
// Take a look at "Channels Mapping" for handling multiple create, update, delete events
// Also, check out "PubSub Implementations" for using Redis instead of PubSub
// PubSub is not recommended for production because it won't work if you have multiple servers
// withFilter makes it so you can only listen to changes to this person instead of all people
subscribe: withFilter(() => pubsub.asyncIterator('personUpdated'), (payload, args) => {
return (payload.personUpdated.id===args.id);
})
}
}
};
export default resolvers;
schema.graphql
enum EyeColor {
brown
blue
green
hazel
}
type Person {
id: ID
name: String
eyeColor: EyeColor
occupation: String
}
type Query {
person(id: ID!): Person
}
type Mutation {
updatePerson(id: ID!, name: String!, eyeColor: EyeColor!, occupation: String!): Person
}
type Subscription {
personUpdated(id: ID!): Person
}
schema {
query: Query
mutation: Mutation
subscription: Subscription
}
A full write-up about this can be found in this Medium post: How to get Apollo 2.0 working with GraphQL + subscriptions.
An example app demonstrating how to use Apollo 2.0 with a GraphQL server + subscriptions can be found here: meteor-apollo2