This is the code from official docs of relay, This is for GraphQLAddTodoMutation
const GraphQLAddTodoMutation = mutationWithClientMutationId({
name: 'AddTodo',
inputFields: {
text: { type: new GraphQLNonNull(GraphQLString) },
},
outputFields: {
todoEdge: {
type: GraphQLTodoEdge,
resolve: ({localTodoId}) => {
const todo = getTodo(localTodoId);
return {
cursor: cursorForObjectInConnection(getTodos(), todo),
node: todo,
};
},
},
viewer: {
type: GraphQLUser,
resolve: () => getViewer(),
},
},
mutateAndGetPayload: ({text}) => {
const localTodoId = addTodo(text);
return {localTodoId};
},
});
I think mutateAndGetPayload executes first then outputFields? since it used localTodoId object as parameter, I see localTodoId object returned from mutateAndGetPayload.
and this is the code for relay mutation.please look at the getFatQuery
export default class AddTodoMutation extends Relay.Mutation {
static fragments = {
viewer: () => Relay.QL`
fragment on User {
id,
totalCount,
}
`,
};
getMutation() {
return Relay.QL`mutation{addTodo}`;
}
getFatQuery() {
return Relay.QL`
fragment on AddTodoPayload #relay(pattern: true) {
todoEdge,
viewer {
todos,
totalCount,
},
}
`;
}
getConfigs() {
return [{
type: 'RANGE_ADD',
parentName: 'viewer',
parentID: this.props.viewer.id,
connectionName: 'todos',
edgeName: 'todoEdge',
rangeBehaviors: ({status}) => {
if (status === 'completed') {
return 'ignore';
} else {
return 'append';
}
},
}];
}
getVariables() {
return {
text: this.props.text,
};
}
getOptimisticResponse() {
return {
// FIXME: totalCount gets updated optimistically, but this edge does not
// get added until the server responds
todoEdge: {
node: {
complete: false,
text: this.props.text,
},
},
viewer: {
id: this.props.viewer.id,
totalCount: this.props.viewer.totalCount + 1,
},
};
}
}
I think the todoEdge is from the outputFields from GraphQL? I see a viewer query on it, why does it need to query the viewer? How do I create a getFatQuery? I would really appreciate if someone help me understand this more and about Relay mutation.
mutateAndGetPayload executes then returns the payload to the outputFields
mutationWithClientMutationId
Source-Code
starWarsSchema example
mutationWithClientMutationId
inputFields: defines the input structures for mutation, where the input fields will be wraped with the input values
outputFields: defines the ouptput structure of the fields after the mutation is done which we can view and read
mutateAndGetPayload: this function is the core one to relay mutations, which performs the mutaion logic (such as database operations) and will return the payload to be exposed to output fields of the mutation.
mutateAndGetPayload maps from the input fields to the output fields using the mutation
operation. The first argument it receives is the list of the input parameters, which we can read to perform the mutation action
The object we return from mutateAndGetPayload can be accessed within the output fields
resolve() functions as the first argument.
getFatQuery() is where we represent, using a GraphQL fragment, everything
in our data model that could change as a result of this mutation
Related
I am learning about Interfaces and Unions in GraphQL (using Apollo Server) and am wondering about something. Using documentation examples, https://www.apollographql.com/docs/apollo-server/schema/unions-interfaces/#union-type, how would I return a result which could return authors and books?
My understanding is that you can only return one object type. If a search result contains and array of both books and authors, how is such a result returned? Can things be structured for this case? I have noticed that __resolveType does not work on an array and can only return a single result (it would return the type for all the objects in the array, not each object in array).
GraphQL TypeDef
const { gql } = require('apollo-server');
const typeDefs = gql`
union Result = Book | Author
type Book {
title: String
}
type Author {
name: String
}
type Query {
search: [Result]
}
`;
Resolver
const resolvers = {
Result: {
__resolveType(obj, context, info){
console.log(obj);
if(obj.name){
return 'Author';
}
if(obj.title){
return 'Book';
}
return null;
},
},
Query: {
search: () => { ... }
},
};
const server = new ApolloServer({
typeDefs,
resolvers,
});
server.listen().then(({ url }) => {
console.log(`🚀 Server ready at ${url}`)
});
The actual GraphQL query may look something like this and consider the search result is both books and authors:
{
search(contains: "") {
... on Book {
title
}
... on Author {
name
}
}
}
When run, __resolveType(obj, context, info){, obj is:
[{ title: 'A' }, { title: 'B' }, { name: 'C' }]
There's only two ways that would happen:
The search field's type is not actually a list (i.e. it's Result instead of [Result] as shown in the code above.
Your resolver for the search field is returning an array of an array of objects: return [[{ title: 'A' }, { title: 'B' }, { name: 'C' }]]
I have the following datamodel:
type Job {
// ...
example: String
selections: [Selection!]
// ...
}
type Selection {
...
question: String
...
}
I define my object type so:
export const Job = prismaObjectType({
name: 'Job',
definition(t) {
t.prismaFields([
// ...
'example',
{
name: 'selections',
},
// ...
])
},
})
I do my resolver this way:
t.field('createJob', {
type: 'Job',
args: {
// ...
example: stringArg(),
selections: stringArg(),
// ...
},
resolve: (parent, {
example,
selections
}, ctx) => {
// The resolver where I do a ctx.prisma.createJob and connect/create with example
},
})
So now in the resolver I can receive the selections as json string and then parse it and connect/create with the job.
The mutation would look like this:
mutation {
createJob(
example: "bla"
selections: "ESCAPED JSON HERE"
){
id
}
}
I was wondering if there's anything more elegant where I could do something like:
mutation {
createJob(
example: "bla"
selections: {
question: "bla"
}
){
id
}
}
or
mutation {
createJob(
example: "bla"
selections(data: {
// ...
})
){
id
}
}
I've noticed that with nexus-prisma you can do stringArg({list: true}) but you can't really do objects.
My main question is what is the most elegant way to do either nested mutation or connect all in one.
You can use an inputObjectType as shown in the docs:
export const SomeFieldInput = inputObjectType({
name: "SomeFieldInput",
definition(t) {
t.string("name", { required: true });
t.int("priority");
},
});
Make sure to include the type as part of the types you pass to makeSchema. You can then use it to define an argument, like
args: {
input: arg({
type: "SomeFieldInput", // name should match the name you provided
}),
}
Now, the argument value will be available to your resolver as a regular JavaScript object, not a String. If you need a list of input objects, or want to make the argument required, you do so using the same options you would provide with when using a scalar -- list, nullable, description, etc.
Here's a complete example:
const Query = queryType({
definition(t) {
t.field('someField', {
type: 'String',
nullable: true,
args: {
input: arg({
type: "SomeFieldInput", // name should match the name you provided
}),
},
resolve: (parent, { input }) => {
return `You entered: ${input && input.name}`
},
})
},
})
const SomeFieldInput = inputObjectType({
name: "SomeFieldInput",
definition(t) {
t.string("name", { required: true });
},
});
const schema = makeSchema({
types: {Query, SomeFieldInput},
outputs: {
...
},
});
Then query it like:
query {
someField(
input: {
name: "Foo"
}
)
}
Or using variables:
query($input: SomeFieldInput) {
someField(input: $input)
}
I'm trying to get my head around graphql-subscriptions and withFilter. Subscriptions without variables work as intended, but if I try to use withFilter, I only get 'Subscription field must return Async Iterable. Received: undefined' error when I try to run the subscription.
Am I doing something wrong with setting up withFilter, are the some incompatibilities with packages I'm using or am I completely missing something obvious here? All queries and mutations work properly, so the basic set up should be fine.
My set up is similar to this (all code snippets are in https://gist.github.com/aqmattil/41e10e7c9f30b8ea964cecdc61c58f20
Package.json
// package.json
"dependencies": {
"apollo-server-express": "^2.0.0-beta.2",
"body-parser": "^1.18.3",
"express": "^4.16.3",
"graphql": "^0.13.2",
"graphql-subscriptions": "^0.5.8",
"subscriptions-transport-ws": "^0.9.11"
}
Mutations
// mutations.js
const mutation = new GraphQLObjectType({
name: 'mutation',
fields: {
addSite: {
type: SiteType,
description: "Create a new Site",
args: {
name: { type: new GraphQLNonNull(GraphQLString) },
location: { type: GraphQLString },
company: { type: GraphQLString }
},
async resolve(parentValue, { name, location, company }) {
const site = await new Site({ name, location, company }).save()
const siteid = site._id;
console.log("addSite resolve", siteid, name, location, company );
pubsub.publish('siteAdded', { 'siteAdded': site } );
return site;
}
}
}
});
module.exports = mutation;
Subscriptions
// subscriptions.js
const graphql = require('graphql');
const {
GraphQLObjectType,
GraphQLString
} = graphql;
const { withFilter } = require('graphql-subscriptions');
const SiteType = require('./site_type');
const pubsub = require('./pubsub_helper');
const Subscriptions = new GraphQLObjectType({
name: 'subscription',
fields: () => ({
/*
// this code works, commented out to test withfilter
siteAdded: {
type: SiteType,
resolve(payload) {
return payload.siteAdded;
},
subscribe() {
return pubsub.asyncIterator('siteAdded');
}
},
*/
// test withFilter
siteAdded: {
type: SiteType,
args: {
name: { type: GraphQLString }
},
resolve(payload) {
return payload.siteAdded;
},
subscribe() {
// this returns undefined
withFilter(
() => {
console.log("in subscribe withfilter");
return pubsub.asyncIterator('siteAdded');
}
),
(payload, variables) => {
console.log("payload, variables", payload, variables);
return true;
}
}
}
})
});
module.exports = Subscriptions;
I'm using graphiql to run the queries,
// this is used to add a site
mutation {
addSite(name:"test name", location: "somewhere") {
id
}
}
// simple subscription - this works as inteded, and new sites are shown
subscription {
siteAdded {
name
location
company {
id
}
}
}
// using query variables --> returns "Subscription
// field must return Async Iterable. Received: undefined"
subscription {
siteAdded(name: "test name") {
name
location
company {
id
}
}
}
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),
};
}
};
I'm writing a deletion mutation. The mutation should delete a Key node and update the viewer's keys collection (I'm using Relay-style collections: viewer { keys(first: 3) { edges { node { ... }}}}.
Following the advice here, I'm using the FIELDS_CHANGE config for simplicity, and it's actually working:
export class DeleteKeyMutation extends Relay.Mutation {
static fragments = {
viewer: () => Relay.QL`
fragment on Viewer { id }
`,
};
getMutation() { return Relay.QL`mutation {deleteKey}`; }
getVariables() {
return {
id: this.props.id,
};
}
getFatQuery() {
return Relay.QL`
fragment on DeleteKeyPayload {
viewer { keys }
}
`;
}
getConfigs() {
return [
{
type: 'FIELDS_CHANGE',
fieldIDs: {
viewer: this.props.viewer.id,
},
},
];
}
}
Now, how should I write an optimistic mutation for this? I've tried different approaches but none worked.
Optimistic update in Relay is just a simulation of what the server will return if operation succeeds. In your case you are removing one key, meaning the result would be an object without that key.
getOptimisticUpdate() {
return {
viewer: {
id: this.props.viewer.id,
keys: {
edges: this.props.viewer.keys.edges.filter((keyEdge) => key.node.id !== this.props.id)
}
}
};
}
You will also need to include the keys to your fragments so they are available in the mutation.
static fragments = {
viewer: () => Relay.QL`
fragment on Viewer { id, keys { edges(first: 3) { node { id } }}
`,
};
The problem with this approach is that it relies on your mutation to know what's your current keys pagination. If you are operating on the whole Connection at once, it is fine, but if you are using Relay pagination you should consider using other mutation operations.
There is NODE_DELETE, which can delete all occurrences of your key from Relay store or you can use RANGE_DELETE to only delete it from your current connection.