Apollo Client Introspective Queries - graphql

Works on the Playground...
I am trying to query the following enum values:
query {
__type(name:"ActivityType") {
enumValues {
name
}
}
}
When I test the query with the playground, it works. I receive a list of the enumeration values for the ActivityType:
Doesn't work with Apollo-Client...
When I run the following code with the client, I receive the expected results for the first query, but not for the second:
import ApolloClient, { gql } from 'apollo-boost'
const FIRST_QUERY = gql`
query successfulQuery {
totalActivities
}
`
const SECOND_QUERY = gql`
query unsuccessfulQuery {
__type(name:"ActivityType") {
enumValues {
name
}
}
}
`
const client = new ApolloClient({ uri: 'http://localhost:4000' })
//
// First Query returns data
//
client.query({ query: FIRST_QUERY })
.then(console.log)
.catch(console.error)
//
// Second Query does not return data
//
client.query({ query: SECOND_QUERY })
.then(console.log)
.catch(console.error)
The results
{ data: {...}, loading: false, networkStatus: 7, stale: false }
{ data: null, loading: false, networkStatus: 7, stale: false }
Is there something special that you have to implement with the client to allow introspective queries?

Related

Apollo nodejs server; How to get mutation/query schema path in the request context when writing a plugin?

I'm writing an Apollo server plugin for node.js, and my goal is to improve my teams debugging experience. My plugin currently looks something like this:
export function eddyApolloPlugin(): ApolloServerPlugin {
return {
requestDidStart(requestContext) {
// Set requestId on the header
const requestId = (requestContext?.context as EddyContext)?.requestId;
if (requestId) {
requestContext.response?.http?.headers.set('requestId', requestId);
}
return {
willSendResponse(context) { // <== Where do I find the "path" in the schema here?
// Inspired by this: https://blog.sentry.io/2020/07/22/handling-graphql-errors-using-sentry
// and the official documentation here: https://docs.sentry.io/platforms/node/
// handle all errors
for (const error of requestContext?.errors || []) {
handleError(error, context);
}
},
};
},
};
}
I would like to know if I can access the path in the schema here? It's pretty easy to find the name of mutaiton/query with operation.operationName, but where can I get the name of the query/mutation as defined in the schema?
Solution
export function eddyApolloPlugin(): ApolloServerPlugin {
return {
requestDidStart(requestContext) {
// Set requestId on the header
const requestId = (requestContext?.context as EddyContext)?.requestId;
if (requestId) {
requestContext.response?.http?.headers.set('requestId', requestId);
}
return {
didResolveOperation(context) {
const operationDefinition = context.document
.definitions[0] as OperationDefinitionNode;
const fieldNode = operationDefinition?.selectionSet
.selections[0] as FieldNode;
const queryName = fieldNode?.name?.value;
// queryName is what I was looking for!
},
};
},
};
}
Your requirement is not very clear. If you want to get the name of the query/mutation to distinguish which query or mutation the client sends.
You could get the name from context.response.data in willSendResponse event handler.
E.g.
server.ts:
import { ApolloServer, gql } from 'apollo-server';
import { ApolloServerPlugin } from 'apollo-server-plugin-base';
import { parse, OperationDefinitionNode, FieldNode } from 'graphql';
function eddyApolloPlugin(): ApolloServerPlugin {
return {
requestDidStart(requestContext) {
return {
didResolveOperation(context) {
console.log('didResolveOperation');
const obj = parse(context.request.query!);
const operationDefinition = obj.definitions[0] as OperationDefinitionNode;
const selection = operationDefinition.selectionSet.selections[0] as FieldNode;
console.log('operationName: ', context.request.operationName);
console.log(`${context.operation!.operation} name:`, selection.name.value);
},
willSendResponse(context) {
console.log('willSendResponse');
console.log('operationName: ', context.request.operationName);
console.log(`${context.operation!.operation} name:`, Object.keys(context.response.data!)[0]);
},
};
},
};
}
const typeDefs = gql`
type Query {
hello: String
}
type Mutation {
update: String
}
`;
const resolvers = {
Query: {
hello() {
return 'Hello, World!';
},
},
Mutation: {
update() {
return 'success';
},
},
};
const server = new ApolloServer({ typeDefs, resolvers, plugins: [eddyApolloPlugin()] });
const port = 3000;
server.listen(port).then(({ url }) => console.log(`Server is ready at ${url}`));
GraphQL Query:
query test {
hello
}
the logs of the server:
didResolveOperation
operationName: test
query name: hello
willSendResponse
operationName: test
query name: hello
GraphQL Mutation:
mutation test {
update
}
the logs of the server:
didResolveOperation
operationName: test
mutation name: update
willSendResponse
operationName: test
mutation name: update

#client Apollo GQL tag breaks query

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.

orderBy: the results from update cache query

Working on a react apollo graphcool project
I've got my mutation update working, however I would like to filter the results, the results only filter on page refresh?
Looking at cache.writeQuery() the docs say get the query and concat to that so i guess thats why its not filtering. Is there anyway to query after?
Here the code from my CreatePost component
import React from 'react';
import gql from "graphql-tag";
import { Mutation } from "react-apollo";
const GET_POSTS = gql`
{
allPosts(orderBy: createdAt_DESC) {
id
title
content
}
}
`;
const CREATE_POST = gql`
mutation createPost($title: String!, $content: String!){
createPost(title: $title, content: $content){
id
title
content
}
}
`;
const udpateCache = (cache, { data: { createPost } }) => {
const { allPosts } = cache.readQuery({ query: GET_POSTS });
cache.writeQuery({
query: GET_POSTS,
data: { allPosts: allPosts.concat([createPost]) }
})
}
const CreatePost = () => {
//vars for the input refs
let titleInput
let contentInput
return (
<Mutation
mutation={CREATE_POST}
update={udpateCache}
>
{createPost => ( //##form and onSubmit ##// ) }
</Mutation>
)
}
export default CreatePost
When you do your writeQuery you also need to pass in any variables used, to make sure you receive the same information from the cache.
const udpateCache = (cache, { data: { createPost } }) => {
const { allPosts } = cache.readQuery({ query: GET_POSTS });
cache.writeQuery({
query: GET_POSTS,
data: { allPosts: allPosts.concat([createPost]) },
variables: { orderBy: /* input */ }
})
}

Apollo GraphQL: Calling a Query Twice with apolloClient.query?

I have the following query:
const GET_MY_USERINFOFORIMS_QUERY = gql`
query($userID: String!){
myUserDataForIMs(userID:userID){
name_first
name_last
picture_medium
}
} `;
const withUserInfoForIMs = graphql(GET_MY_USERINFOFORIMS_QUERY, {
options({ userID }) {
return {
variables: { userID: `${userID}`}
};
}
,
props({ data: { loading, myUserDataForIMs } }) {
return { loading, myUserDataForIMs };
},
name: 'GET_MY_USERINFOFORIMS_QUERY',
});
From the Apollo docs, it looks like I may be able to call this query twice from inside the component, using apolloClient.query, doing something like this:
client.query({ query: query1 })
client.query({ query: query2 })
Is there a way to call the query twice, passing a different userID each time?
Found it. :)
const localThis = this;
this.props.ApolloClientWithSubscribeEnabled.query({
query: GET_MY_USERINFOFORIMS_QUERY,
variables: {userID: fromID},
}).then((result) => {
localThis.setState({ fromAvatar: result.data.myUserDataForIMs[0].picture_thumbnail });
});
this.props.ApolloClientWithSubscribeEnabled.query({
query: GET_MY_USERINFOFORIMS_QUERY,
variables: {userID: toID},
}).then((result) => {
localThis.setState({ toAvatar: result.data.myUserDataForIMs[0].picture_thumbnail });
});
If there's a better/more efficient way, please post it.
You can do this by passing Apollo's refetch() method into your component's props alongside the data:
const withUserInfoForIMs = graphql(GET_MY_USERINFOFORIMS_QUERY, {
options({ userID }) {
return {
variables: { userID: `${userID}`}
};
},
props({ data: { refetch, loading, myUserDataForIMs } }) {
return { refetch, loading, myUserDataForIMs };
},
name: 'GET_MY_USERINFOFORIMS_QUERY',
});
...then somewhere in your component, you can refetch the data "manually":
theUserWasChangedSomehow(userID) {
this.props.refetch({ userID });
}

GraphQL how to mutate data

I have a basic schema for mutating some data which looks like
const schema = new graphql.GraphQLSchema({
mutation: new graphql.GraphQLObjectType({
name: 'Remove',
fields: {
removeUser: {
type: userType,
args: {
id: { type: graphql.GraphQLString }
},
resolve(_, args) {
const removedData = data[args.id];
delete data[args.id];
return removedData;
},
},
},
})
});
Looking around google I cant find a clear example of the example query which needs to be sent to mutate.
I have tried
POST -
localhost:3000/graphql?query={removeUser(id:"1"){id, name}}
This fails with error:
{
"errors": [
{
"message": "Cannot query field \"removeUser\" on type \"Query\".",
"locations": [
{
"line": 1,
"column": 2
}
]
}
]
}
In order to post requests from the front-end application it is recommended to use apollo-client package. Say i wanted to validate a user login information:
import gql from 'graphql-tag';
import ApolloClient, {createNetworkInterface} from 'apollo-client';
client = new ApolloClient({
networkInterface: createNetworkInterface('http://localhost:3000/graphql')
});
remove(){
client.mutate({
mutation: gql`
mutation remove(
$id: String!
) {
removeUser(
id: $id
){
id,
name
}
}
`,
variables: {
id: "1"
}
}).then((graphQLResult)=> {
const { errors, data } = graphQLResult;
if(!errors && data){
console.log('removed successfully ' + data.id + ' ' + data.name);
}else{
console.log('failed to remove');
}
})
}
More information about apollo-client can be found here
Have you tried using graphiql to query and mutate your schema?
If you'd like to create a POST request manually you might wanna try to struct it in the right form:
?query=mutation{removeUser(id:"1"){id, name}}
(Haven't tried POSTing myself, let me know if you succeeded, i structured this out of the url when using graphiql)
You have to explicitly label your mutation as such, i.e.
mutation {
removeUser(id: "1"){
id,
name
}
}
In GraphQL, if you leave out the mutation keyword, it's just a shorthand for sending a query, i.e. the execution engine will interpret it as
query {
removeUser(id: "1"){
id,
name
}
}
cf. Section 2.3 of the GraphQL Specification
const client = require("../common/gqlClient")();
const {
createContestParticipants,
} = require("../common/queriesAndMutations");
const gql = require("graphql-tag");
const createPartpantGql = async (predictObj) => {
try {
let resp = await client.mutate({
mutation: gql(createContestParticipants),
variables: {
input: {
...predictObj,
},
},
});
let contestParticipantResp = resp.data.createContestParticipants;
return {
success: true,
data: contestParticipantResp,
};
} catch (err) {
console.log(err.message)
console.error(`Error creating the contest`);
return {
success: false,
message: JSON.stringify(err.message),
};
}
};

Resources