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
Related
I am currently building chat app in Next JS. I use graphql with Apollo Client in frontend and Apollo Server on backend. Now I want real time updates, but I found out there is no support for Subscription in apollo-server-micro. As they write here 😢 :
https://www.apollographql.com/docs/apollo-server/data/subscriptions#enabling-subscriptions
Beginning in Apollo Server 3, subscriptions are not supported by the
"batteries-included" apollo-server package. To enable subscriptions,
you must first swap to the apollo-server-express package (or any other
Apollo Server integration package that supports subscriptions).
But I can not use apollo-server-express because I use NextAuth for authentication and then I pass it to context:
export async function createContext({
req,
res,
}: {
req: NextApiRequest;
res: NextApiResponse;
}): Promise<Context> {
const session = await getSession({ req });
const user = { ...session?.user, _id: session?.userId } as User;
const db = await dbConnect();
return {
user,
db,
};
}
Thanks for help 👍.
I found a solution, for everyone who wants to use graphql subscriptions on server: Use graphql-yoga instead :
https://www.graphql-yoga.com/docs/features/subscriptions
My code for pages/api/graphql.ts :
import { createServer, createPubSub, PubSub } from "#graphql-yoga/node";
import { NextApiRequest, NextApiResponse } from "next";
import { Session } from "next-auth";
import { getSession } from "next-auth/react";
const pubSub = createPubSub<{
"user:newMessage": [userId: string, message: Message];
"user:newChat": [userId: string, chat: Chat];
}>();
export type pubSub = typeof pubSub;
const server = createServer<
{
req: NextApiRequest;
res: NextApiResponse;
},
{
user: User;
pubSub: any;
}
>({
context: async ({ req }) => {
const session = await getSession({ req });
await dbConnect();
return {
user: { ...session?.user, _id: session?.userId } as User,
pubSub,
};
},
schema: {
typeDefs,
resolvers: {
Query,
Mutation,
Subscription,
},
},
});
export default server;
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.
I am trying to make local state management with vue apollo, but even after following the docs I am getting no result. There is no error in the console so I am not sure what is wrong.
Here is my setup:
// main.js file the initializing part
const client = new ApolloClient({
link: ApolloLink.from([
errorLink,
authMiddleware,
link,
]),
cache,
typeDefs,
resolvers,
connectToDevTools: true,
});
// resolvers file
import gql from 'graphql-tag';
import { todoItemsQuery } from './task.queries';
export const typeDefs = gql`
type Item {
id: ID!
text: String!
done: Boolean!
}
type Mutation {
changeItem(id: ID!): Boolean
deleteItem(id: ID!): Boolean
addItem(text: String!): Item
}
`;
export const resolvers = {
Mutation: {
checkItem: (_, { id }, { cache }) => {
const data = cache.readQuery({ query: todoItemsQuery });
console.log('data res', data);
const currentItem = data.todoItems.find(item => item.id === id);
currentItem.done = !currentItem.done;
cache.writeQuery({ query: todoItemsQuery, data });
return currentItem.done;
},
},
};
//queries file
import gql from 'graphql-tag';
export const todoItemsQuery = gql`
{
todoItems #client {
id
text
done
}
}
`;
export const checkItemMutation = gql`
mutation($id: ID!) {
checkItem(id: $id) #client
}
`;
// component where I call it
apollo: {
todoItems: {
query: todoItemsQuery
}
},
checkItem(id) {
this.$apollo
.mutate({
mutation: checkItemMutation,
variables: { id }
})
.then(({ data }) => {
console.log("CACHE", data);
});
},
I get empty todoItems, no errors.
Please let me know what am I missing, I am not grasping some concept I think, and if there is a way to use vuex with apollo then I can do that too.
Foreword: I'm not an Apollo specialist, just starting to use it.
Just in case, these thoughts may help you. If they don't, well, I'm sorry.
In main.js: Apollo documentation provides a slightly different set up when using Apollo Boost (https://www.apollographql.com/docs/link/links/state/#with-apollo-boost). Just in case, this is how I have set up my implementation so far.
import VueApollo from 'vue-apollo'
import ApolloClient from 'apollo-boost';
import { InMemoryCache } from 'apollo-cache-inmemory';
const cache = new InMemoryCache();
Vue.use(VueApollo)
const apolloClient = new ApolloClient({
//...whatever you may already have,
clientState: {
// "defaults" is your initial state - if empty, I think it might error out when your app launches but is not hydrated yet.
defaults: {
todoItems: [],
}
cache,
typeDefs: {...yourTypeDefs},
resolvers: {...yourResolvers},
},
});
const apolloProvider = new VueApollo({
defaultClient: apolloClient,
});
In your typeDefs: I can not see a Query Type for your todoItems:
type Query {
todoItemsQuery: [Item]
}
In your component: my own implementation was not working until I added an update method to the apollo request:
apollo: {
todoItems: {
query: todoItemsQuery,
update: data => data.todoItems
}
},
I have a vue-apollo (using nuxt) query that is supposed to have a local client field show. However, when I have the show #client line included in the query the component does not render. For some reason it also seems to fail silently.
query myAccounts {
accounts: myAccounts {
email
calendars {
id
name
hex_color
is_enabled
show #client
}
}
}
I am extending the Calendar type in an extensions.js file (pasted below) with two mutations.
import gql from 'graphql-tag'
export const typeDefs = gql`
extend type Calendar {
show: Boolean
}
type Mutation {
showCalendar(id: ID!): Boolean
hideCalendar(id: ID!): Boolean
}
`
Here is the resolver that sets the value, along with the Apollo config:
import { InMemoryCache } from 'apollo-cache-inmemory'
import { typeDefs } from './extensions'
import MY_ACCOUNTS_QUERY from '~/apollo/queries/MyAccounts'
const cache = new InMemoryCache()
const resolvers = {
Mutation: {
showCalendar: (_, { id }, { cache }) => {
const data = cache.readQuery({ query: MY_ACCOUNTS_QUERY })
const found = data.accounts
.flatMap(({ calendars }) => calendars)
.find(({ id }) => id === '1842')
if (found) {
found.show = true
}
cache.writeQuery({ query: todoItemsQuery, data })
return true
}
}
}
export default context => {
return {
cache,
typeDefs,
resolvers,
httpLinkOptions: {
credentials: 'same-origin'
},
}
}
along with the nuxt config:
apollo: {
defaultOptions: {
$query: {
loadingKey: 'loading',
fetchPolicy: 'cache-and-network',
},
},
errorHandler: '~/plugins/apollo-error-handler.js',
clientConfigs: {
default: '~/apollo/apollo-config.js'
}
}
Querying local state requires the state to exist (i.e. it should be initialized) or for a local resolver to be defined for the field. Apollo will run the resolver first, or check the cache directly for the value if a resolver is not defined. There's not really a good way to initialize that value since it's nested inside a remote query, so you can add a resolver:
const resolvers = {
Calendar: {
show: (parent) => !!parent.show,
},
// the rest of your resolvers
}
See the docs for additional examples and more details.
I am trying out the basic implementation for Apollo server for GraphQL with my REST API calls as Data Sources. I do not see any data returned from the same even though there is data returned when I call the API separately. Can anyone help figure out what could be going wrong?
PS: I have CORS enabled on my API so not sure if I am passing that too correctly. I do not have any idea how to figure out what URL this is calling.
My sample code below:
const { ApolloServer, gql } = require('apollo-server');
const { RESTDataSource } = require('apollo-datasource-rest');
class Contact extends RESTDataSource {
constructor() {
super();
this.baseURL = 'http://localhost:8080/objects/';
}
async getContactById(id) {
return this.get(`contact/${id}`);
}
async getAllContacts() {
const data = await this.get(`contact`);
return data.results;
}
// an example making an HTTP PUT request
async newContact(contact) {
return this.put(
'contact', // path
contact, // request body
);
}
};
// Type definitions define the "shape" of your data and specify
// which ways the data can be fetched from the GraphQL server.
const typeDefs = gql`
# Comments in GraphQL are defined with the hash (#) symbol.
type Query {
allContacts: [Contact]
contactById(id: ID): Contact
}
type Contact {
id: ID
contact_name: String
}
`;
// Resolvers define the technique for fetching the types in the
// schema.
const resolvers = {
Query: {
contactById: async (_source, { id }, { dataSources }) => {
return dataSources.contact.getContactById(id);
},
allContacts: async (_source, _args, { dataSources }) => {
return dataSources.contact.getAllContacts();
},
},
};
// In the most basic sense, the ApolloServer can be started
// by passing type definitions (typeDefs) and the resolvers
// responsible for fetching the data for those types.
const server = new ApolloServer({
typeDefs,
resolvers,
dataSources: () => {
return {
contact : new Contact(),
};
},
cors : true,
});
// This `listen` method launches a web-server. Existing apps
// can utilize middleware options, which we'll discuss later.
server.listen().then(({ url }) => {
console.log(`🚀 Server ready at ${url}`);
});
Below is the request and response from the GraphQL playground:
query {
contactById (id : 5) {
id
contact_name
}
}
Response:
{
"data": {
"contactById": {
"id": null,
"contact_name": null
}
}
}