Good day im newbie here and im tackling graphql and im having some problem on mutation can someone explain this block of code for me thank you
RootMutation: {
createAuthor: (root, args) => { return Author.create(args); },
createPost: (root, { authorId, tags, title, text }) => {
return Author.findOne({ where: { id: authorId } }).then( (author) => {
console.log('found', author);
return author.createPost( { tags: tags.join(','), title, text });
});
},
},
Sure, this is an example of two mutations in a GraphQL server. We can break it down to understand what is going on.
First let's look at the type system. A GraphQL schema normally has two root fields query and mutation (and sometimes subscription). These root fields are the root of your data hierarchy and expose the queries (GET requests) and mutations (POST, PUT, DELETE, etc requests) that you have access to.
By the looks of it you are implementing a schema with a root mutation type that looks like this:
type Mutation {
createAuthor: Author
createPost: Post
}
A type in GraphQL is made up of a set of fields each of which can have an associated resolver. Resolvers in GraphQL are like the event handlers you would attach to endpoints in REST.
The code that you have above is defining two resolvers that will handle the logic associated with the createAuthor and createPost mutations. I.E. the code in the createPost resolver is what will be run when I issue a query like this:
mutation CreatePost($post: CreatePostInput!) {
createPost(input: $post) {
id
title
tags
text
}
}
The GraphQL runtime parses the query and routes the operation to the correct resolver depending on the content of the query. In this example, it would see that I am calling the createPost mutation and would make sure to call the createPost resolver which in your case looks like this:
createPost: (root, { authorId, tags, title, text }) => {
return Author.findOne({ where: { id: authorId } }).then( (author) => {
console.log('found', author);
return author.createPost( { tags: tags.join(','), title, text });
});
},
To understand how a resolver works, let's look at the GraphQLFieldResovler type definition from graphql-js
export type GraphQLFieldResolver<TSource, TContext> = (
source: TSource,
args: { [argName: string]: any },
context: TContext,
info: GraphQLResolveInfo
) => mixed;
As you can see a GraphQLFieldResolver is a function that takes 4 arguments.
source: The source is the parent object of the current field. For example if you were defining a resolver for a field fullName on the User type, the source would be the full user object.
args: The args are any input arguments for that resolver. In my query above it would contain the value of the $post variable.
context: Context is a global context for a GraphQL execution. This is useful for passing information around that a resolver might need. For example, you include a database connection that you can use from your resolvers without importing it in every file.
info: The info object contains information about your GraphQL schema, the query, and other information such as the path to the current resolver being executed. This is useful in many ways. Here is one post talking about how you can use it to precompute queries: (https://scaphold.io/community/blog/querying-relational-data-with-graphql/)
This idea of having types and field resolvers is part of what makes GraphQL so powerful. Once you've defined you type system and the resolvers for their fields you can structure your schema however you want and GraphQL will always make sure to call the correct resolver no matter how deeply nested a query might be.
I hope this helps :)
Related
I have written a GraphQL query which like the one below:
{
posts {
author {
comments
}
comments
}
}
I want to know how can I get the details about the requested child fields inside the posts resolver.
I want to do it to avoid nested calls of resolvers. I am using ApolloServer's DataSource API.
I can change the API server to get all the data at once.
I am using ApolloServer 2.0 and any other ways of avoiding nested calls are also welcome.
You'll need to parse the info object that's passed to the resolver as its fourth parameter. This is the type for the object:
type GraphQLResolveInfo = {
fieldName: string,
fieldNodes: Array<Field>,
returnType: GraphQLOutputType,
parentType: GraphQLCompositeType,
schema: GraphQLSchema,
fragments: { [fragmentName: string]: FragmentDefinition },
rootValue: any,
operation: OperationDefinition,
variableValues: { [variableName: string]: any },
}
You could transverse the AST of the field yourself, but you're probably better off using an existing library. I'd recommend graphql-parse-resolve-info. There's a number of other libraries out there, but graphql-parse-resolve-info is a pretty complete solution and is actually used under the hood by postgraphile. Example usage:
posts: (parent, args, context, info) => {
const parsedResolveInfo = parseResolveInfo(info)
console.log(parsedResolveInfo)
}
This will log an object along these lines:
{
alias: 'posts',
name: 'posts',
args: {},
fieldsByTypeName: {
Post: {
author: {
alias: 'author',
name: 'author',
args: {},
fieldsByTypeName: ...
}
comments: {
alias: 'comments',
name: 'comments',
args: {},
fieldsByTypeName: ...
}
}
}
}
You can walk through the resulting object and construct your SQL query (or set of API requests, or whatever) accordingly.
Here, are couple main points that you can use to optimize your queries for performance.
In your example there would be great help to use
https://github.com/facebook/dataloader. If you load comments in your
resolvers through data loader you will ensure that these are called
just once. This will reduce the number of calls to database
significantly as in your query is demonstrated N+1 problem.
I am not sure what exact information you need to obtain in posts
ahead of time, but if you know the post ids you can consider to do a
"look ahead" by passing already known ids into comments. This will
ensure that you do not need to wait for posts and you will avoid
graphql tree calls and you can do resolution of comments without
waiting for posts. This is great article for optimizing GraphQL
waterfall requests and might you give good idea how to optimize your
queries with data loader and do look ahead
https://blog.apollographql.com/optimizing-your-graphql-request-waterfalls-7c3f3360b051
I am using graphql-tools. After receiving a GraphQL query, I execute a search using ElasticSearch and return the data.
However, usually the requested query includes only a few of the possible fields, not all. I want to pass only the requested fields to ElasticSearch.
First, I need to get the requested fields.
I can already get the whole query as a string. For example, in the resolver,
const resolvers = {
Query: {
async user(p, args, context) {
//can print query as following
console.log(context.query)
}
.....
}
}
It prints as
query User { user(id:"111") { id name address } }
Is there any way to get the requested fields in a format like
{ id:"", name:"", address:"" }
In graphql-js resolvers expose a fourth argument called resolve info. This field contains more information about the field.
From the GraphQL docs GraphQLObjectType config parameter type definition:
// See below about resolver functions.
type GraphQLFieldResolveFn = (
source?: any,
args?: {[argName: string]: any},
context?: any,
info?: GraphQLResolveInfo
) => any
type GraphQLResolveInfo = {
fieldName: string,
fieldNodes: Array<Field>,
returnType: GraphQLOutputType,
parentType: GraphQLCompositeType,
schema: GraphQLSchema,
fragments: { [fragmentName: string]: FragmentDefinition },
rootValue: any,
operation: OperationDefinition,
variableValues: { [variableName: string]: any },
}
In the fieldNodes field you can search for your field and get the selectionSet for the particular field. From here it gets tricky since the selections can be normal field selections, fragments or inline fragments. You would have to merge all of them to know all fields that are selected on a field.
There is an info object passed as the 4th argument in the resolver. This argument contains the information you're looking for.
It can be helpful to use a library as graphql-fields to help you parse the graphql query data:
const graphqlFields = require('graphql-fields');
const resolvers = {
Query: {
async user(_, args, context, info) {
const topLevelFields = graphqlFields(info);
console.log(Object.keys(topLevelFields)); // ['id', 'name', 'address']
},
};
Similarly for graphql-java you may do the same by extending the field parameters with myGetUsersResolverMethod(... DataFetchingEnvironment env).
This DataFetchingEnvironment would be injected for you and you can traverse through this DataFetchingEnvironment object for any part of the graph/query.
This Object allows you to know more about what is being fetched and what arguments have been provided.
Example:
public List<User> getUsers(final UsersFilter filter, DataFetchingEnvironment env) {
DataFetchingFieldSelectionSet selectionSet = env.getSelectionSet();
selectionSet.getFields(); // <---List of selected fields
selectionSet.getArguments(); // <--- Similarly but MAP
...
}
In fact you may be alluding to look ahead data fetching. The above should give you enough insights into the fields requested and you can take it from there to tailor you downstream calls manually. But also you may look into a more efficient way to do this by using the data fetchers for Building efficient data fetchers by looking ahead
I'm working with GraphQL and having some trouble finding the best way to pipe variables from the query to the result.
I have a schema like so:
type Fragment {
# The id of the fragment
id: String!
# The key of the fragment
key: String!
# The type of component
component_type: String!
# The params used to build the fragment
params: JSON
# Component data
data: JSON
children: [JSON]
items: [JSON]
}
The fragment is meant as a "cms" fragment. I want to pass some query data through to another backend after this resolves.
My query looks like this:
query getFragmentsWithItems($keys: [String!]!
$platform: PlatformType
$version: String
$userInfo: UserInput
$userId: Int
) {
fragmentsWithItems(keys: $keys, platform: $platform, version: $version, userInfo: $userInfo, userId: $userId) {
key
data
children
params
items
}
}
Here's the problem: I have some query data in the data field from the Fragment. That data is not available until that Fragment has resolved. I want to take that data and send it to a different backend. I want to do this with GraphQL, and I was hoping to do something like:
Fragment: () => {
async query(obj, args, context, info, {modles}) => {
const items = await models.getItems(obj.query_string);
}
}
But I need the user_info and user_id that I passed to the original query. Apparently that is only accessible from the info argument which is not meant to be used.
The other path I've taken is to have a manual resolver that does something like so:
const resolveFI = ({ keys, platform, version, userInfo, userId, models }) => {
if (!keys || !keys.length) {
return Promise.resolve(null);
}
return models.release.get({ platform, version }).then(release =>
Promise.all(
keys.map(key =>
models.fragments.get({
key,
platform,
version,
release: release.id
})
)
).then(data => {
const promises = [];
data.rows.forEach(r => {
if (r.data.query_data) {
const d = {
// Can just ignore
filters: r.data.query_data.filters || {},
user_info: userInfo,
user_id: userId
};
promises.push(
new Promise(resolve => {
resolve(
models.itemSearch.get(d).then(i => ({ items: i.items, ...r }))
);
})
);
}
...etc other backends
This works, however a manual promise chain seems to defeat the purpose of using GraphQL.
The last thing I tried was making items a non-scalar type, something like:
type Fragment {
items: ItemSearchResult(user_info: UserInput) etc
But since I can't pipe the actual result from Fragment to the ItemSearchResult that doesn't work.
I realize this is pretty long-winded so I'm open to edits or clarifying.
I'm looking to see if I've missed a better approach or if I should just bag it and have the client apps do the item query after they get the Fragment data back.
It's not that you're not supposed to use info -- it's just a tremendous pain in the butt to use ;) In all seriousness, it's meant to be used for optimization and more advanced use cases, so you shouldn't hesitate to use it if a better solution doesn't present itself. There are libraries out there (like this one) that you can use to parse the object more easily.
That said, there's a couple of ways I imagine you could handle this:
1.) Inside your query resolver(s)
getFragmentsWithItems: async (obj, args, ctx, info) => {
const fragments = await howeverYouDoThat()
const backendCalls = fragments.map(fragment => {
// extract whatever data you need from the fragment
return asyncCallToBackEnd()
})
await backendCalls
return fragments
}
Unfortunately, if you have a lot of different queries returning fragments, you'll end up with redundancy.
2.) Inside the resolver for an existing field (or an additional one) on the Fragment type.
If you go this route, and you need args passed to the query field, you can extract them using the info. Alternatively, you can also mutate the context object inside your query resolver and attach those arguments to it. Then, all resolvers "below" the query resolver (like the resolvers for your Fragment fields) can access those arguments through the context.
3.) Apollo Server lets you define a formatResponse function when configuring its middleware. This essentially provides a hook to do whatever you want with the response before it's returned to the client. You could parse the response inside that function and make the calls to the other backend from there.
I am using graphql-tools. After receiving a GraphQL query, I execute a search using ElasticSearch and return the data.
However, usually the requested query includes only a few of the possible fields, not all. I want to pass only the requested fields to ElasticSearch.
First, I need to get the requested fields.
I can already get the whole query as a string. For example, in the resolver,
const resolvers = {
Query: {
async user(p, args, context) {
//can print query as following
console.log(context.query)
}
.....
}
}
It prints as
query User { user(id:"111") { id name address } }
Is there any way to get the requested fields in a format like
{ id:"", name:"", address:"" }
In graphql-js resolvers expose a fourth argument called resolve info. This field contains more information about the field.
From the GraphQL docs GraphQLObjectType config parameter type definition:
// See below about resolver functions.
type GraphQLFieldResolveFn = (
source?: any,
args?: {[argName: string]: any},
context?: any,
info?: GraphQLResolveInfo
) => any
type GraphQLResolveInfo = {
fieldName: string,
fieldNodes: Array<Field>,
returnType: GraphQLOutputType,
parentType: GraphQLCompositeType,
schema: GraphQLSchema,
fragments: { [fragmentName: string]: FragmentDefinition },
rootValue: any,
operation: OperationDefinition,
variableValues: { [variableName: string]: any },
}
In the fieldNodes field you can search for your field and get the selectionSet for the particular field. From here it gets tricky since the selections can be normal field selections, fragments or inline fragments. You would have to merge all of them to know all fields that are selected on a field.
There is an info object passed as the 4th argument in the resolver. This argument contains the information you're looking for.
It can be helpful to use a library as graphql-fields to help you parse the graphql query data:
const graphqlFields = require('graphql-fields');
const resolvers = {
Query: {
async user(_, args, context, info) {
const topLevelFields = graphqlFields(info);
console.log(Object.keys(topLevelFields)); // ['id', 'name', 'address']
},
};
Similarly for graphql-java you may do the same by extending the field parameters with myGetUsersResolverMethod(... DataFetchingEnvironment env).
This DataFetchingEnvironment would be injected for you and you can traverse through this DataFetchingEnvironment object for any part of the graph/query.
This Object allows you to know more about what is being fetched and what arguments have been provided.
Example:
public List<User> getUsers(final UsersFilter filter, DataFetchingEnvironment env) {
DataFetchingFieldSelectionSet selectionSet = env.getSelectionSet();
selectionSet.getFields(); // <---List of selected fields
selectionSet.getArguments(); // <--- Similarly but MAP
...
}
In fact you may be alluding to look ahead data fetching. The above should give you enough insights into the fields requested and you can take it from there to tailor you downstream calls manually. But also you may look into a more efficient way to do this by using the data fetchers for Building efficient data fetchers by looking ahead
I've been playing with GraphQL recently, and am currently learning about mutations. I'm a bit confused with something. I have a model Post with relation Comments. I have a mutation that looks like this:
mutation addCommentToPost {
updatePost(
id: "POST-1",
comments: [{
body: "Hello!"
}]
) {
id,
comments {
id,
body
}
}
}
The problem is, whenever I run this, it seems to remove all the comments and sets the comments to only the one I just added. To be more specific, how do I write a mutation that pushes to the comments array rather than replacing it?
You are using a mutation called updatePosts, which I assume (based on the name) simply updates a post by replacing the fields that are passed. If you want to use the updatePosts mutation to add a comment, you will first have to query for the post to get the current list of comments, add your comment to the end, and then call updateComment with the entire list of comments (including the one that you just added to the end).
However, this isn't really a good solution, especially if the list of comments is potentially very long. If you have the ability to change the GraphQL server, you should create a new mutation on the server with a signature like addComment(postId: ID, comment: CommentInput). In the resolve function for that mutation, simply add the comment that is passed to the end of the list of current comments.
// resolver for addComment:
addComment(root, args) {
// validate inputs here ...
const post = db.getPost(args.postId);
post.comments.append(args.comment);
db.writePost(post.id, post);
}
db.getPost and db.writePost are functions you have to define yourself to retrieve/write a post from/to wherever you store it.
It's important to note that unlike a SQL or Mongo query, a GraphQL mutation itself doesn't have any meaning without the resolve functions. What the mutation does is defined entirely inside its resolve function. Mutation names and arguments only gain meaning together with the resolve function. It's up to you (or the GraphQL server developers in your company) to write the resolve functions.
The way this situation is currently solved in the Graphcool API is to use a create mutation for the Comment that links to the Post. This is called a nested connect mutation.
This is how it would look like:
mutation {
createComment(
text: "Hello!"
postId: "POST-1"
) {
id
text
post {
comments {
id
}
}
}
}
In the future, other nested arguments like comments_set or comments_push could be introduced, then pushing would be possible like this:
mutation addCommentToPost {
updatePost(
id: "POST-1",
comments_push: [{
body: "Hello!"
}]
) {
id,
comments {
id,
body
}
}
}
Disclosure: I work at Graphcool.
You can use those code as an example for mutation.
module.exports = (refs) => ({
type: refs.commentType,
args: {
id: {
type: GraphQLString
},
body: {
type: GraphQLString
}
},
resolve: (parent, args, root) => {
return createUser(args);
}
});