I cannot find some example of a schema where the mutations are nested in it's own resolver, to group some actions in a sub-mutations object.
Something like this:
schema {
query: Query
mutation: RootMutation
}
type RootMutation {
user: UserMutation
post: PostMutation
}
type UserMutation {
create(user: UserInput!): User
delete(id: ID!): Bool
}
type PostMutation {
create(user: PostInput!): Post
delete(id: ID!): Bool
}
And the query would be like this:
mutation CreateUser($u: UserInput!) {
user {
create($u) {
id
}
}
}
I think that, this approach is completely ok with the specification. So why does everybody design the mutations as a first class function? Like so:
type RootMutation {
createUser(user: UserInput!): User
deleteUser(id: ID!): Bool
createPost(user: UserInput!): User
deletePost(id: ID!): Bool
}
When you got a lot of actions to make on your huge system, then the RootMutation will have just an endless list of similar functions (like create, delete and so on).
And isn't this a thing in REST? Where every function is it's own endpoint? But there it's better because you have the HTTP methods. So you would have an /user endpoint with a POST and a DELETE method and so on.
Related
Suppose my graphql API allows me to manage ModelA, ModelB, ModelC.
Those models have very simple typeDefs:
type ModelA {
id: ID!
tags: [SomeComplexTagType!]
}
type ModelB {
id: ID!
tags: [SomeComplexTagType!]
}
type ModelC {
id: ID!
tags: [SomeComplexTagType!]
}
In order for users of the API to add tags to ModelA, ModelB, ModelC the following mutations are provided:
type Mutation {
addTagToModelA(id: ID!, tag: SomeComplexTagType!): Boolean
addTagToModelB(id: ID!, tag: SomeComplexTagType!): Boolean
addTagToModelC(id: ID!, tag: SomeComplexTagType!): Boolean
}
What is the graphql recommended way to get rid of the repetive nature in this API design?
As far as I understand mutations are always top level elements of the Mutation type in graphql. That means, my resolver function for addTagToModelX will never be passed an instance of ModelX as its parent, i.e. something like this will never work:
type Mutation {
ModelA(id: ID!) {
addTagToX(tag: SomeComplexTagType!): Boolean
}
ModelB(id: ID!) {
addTagToX(tag: SomeComplexTagType!): Boolean
}
ModelC(id: ID!) {
addTagToX(tag: SomeComplexTagType!): Boolean
}
}
I found a good solution while digging into the GitHub API.
Summary:
They use global node ids which allow them to conclude the object type from the ID, because the ID is base64 encoded and contains that information.
For my scenario this would mean:
2.1) Make ModelA, ModelB, ModelC implement Taggable
2.2) Introduce mutation addTagToTaggable accepting an ID from Taggable
2.3) In the resolver extract the object type from ID, query object from database, add Tag etc.
2.4) Profit
I am performing a request for an individual post from Apollo Server / Express backend.
In the Apollo GraphQL sandbox, the query works and retrieves the correct post, however, the query has a red squiggle identifying an error which reads -
Variable "$getPostId" is never used in operation "Query".
The query is as follows -
query Query($getPostId: ID!) {
getPost(id:"20c9b3ac-afe6-4faa-a3f9-e00ef1b38ccf") {
title
author
id
}
}
The schema is as follows -
module.exports = gql`
type Post {
id: ID!
title: String!
author: String!
}
type Query {
getPosts: [Post]!
getPost(id: ID!): Post
}
...
`
The closest post which seems to address a similar problem I could find is here. However, I can't translate the resolution to my problem.
Why is the error showing (particularly when the query runs successfully)? What needs to be done to stop the error from showing?
Many thanks!
It sounds like
query Query($getPostId: ID!) {
getPost(id:"20c9b3ac-afe6-4faa-a3f9-e00ef1b38ccf") {
title
author
id
}
}
is supposed to be
query Query($getPostId: ID!) {
getPost(id: $getPostId) {
title
author
id
}
}
Or if your query is actually meant to hard-code the ID, then you want
query Query {
getPost(id:"20c9b3ac-afe6-4faa-a3f9-e00ef1b38ccf") {
title
author
id
}
}
I'm trying to create a mutation that calls a child resolver in addition to the parent resolver if an optional parameter is sent in.
I'm using AWS AppSync to sent my queries to Lambda. AppSync creates and sends an AppSyncEvent to my resolver file that looks something like this:
{
"info": {
"parentTypeName": "Mutation",
"selectionSetList": [
...
],
"selectionSetGraphQL": "...",
"fieldName": "updateUser",
"variables": {}
}
}
This event gets passed to my lambda function where, based on the fieldName and parentTypeName, I call my updateUser function.
I have the below schema
schema {
query: Query
mutation: Mutation
}
type Query {
getUser(id: ID!): User
}
type Mutation {
updateUser(name: String, email: String, bookRead: BookReadInput): User
}
type User {
name: String
email: String
booksRead: [Book]
}
type Book {
title: String
author: String
}
type BookReadInput {
title: String
author: String
}
I want that if the mutation gets passed bookRead then it will know to call a child resolver called addBook besides for the regular updateUser resolver.
I've seen various articles about implementing child resolvers but I can't figure out how they can work with lambda and the way my resolvers work.
The lambda could inspect the selectionSetList and decide what to do with the BookReadInput fields.
See https://aws.amazon.com/blogs/mobile/appsync-and-the-graphql-info-object/
You could also go with pipeline resolvers to first update the user, and then add the book.
I don't think there is a way to have it automated. You need to set it up, one way or the other.
Given a GraphQL schema and resolvers for Apollo Server, and a GraphQL query, is there a way to create a collection of all requested fields (in an Object or a Map) in the resolver function?
For a simple query, it's easy to recreate this collection from the info argument of the resolver.
Given a schema:
type User {
id: Int!
username: String!
roles: [Role!]!
}
type Role {
id: Int!
name: String!
description: String
}
schema {
query: Query
}
type Query {
getUser(id: Int!): User!
}
and a resolver:
Query: {
getUser: (root, args, context, info) => {
console.log(infoParser(info))
return db.Users.findOne({ id: args.id })
}
}
with a simple recursive infoParser function like this:
function infoParser (info) {
const fields = {}
info.fieldNodes.forEach(node => {
parseSelectionSet(node.selectionSet.selections, fields)
})
return fields
}
function parseSelectionSet (selections, fields) {
selections.forEach(selection => {
const name = selection.name.value
fields[name] = selection.selectionSet
? parseSelectionSet(selection.selectionSet.selections, {})
: true
})
return fields
}
The following query results in this log:
{
getUser(id: 1) {
id
username
roles {
name
}
}
}
=> { id: true, username: true, roles: { name: true } }
Things get pretty ugly pretty soon, for example when you use fragments in the query:
fragment UserInfo on User {
id
username
roles {
name
}
}
{
getUser(id: 1) {
...UserInfo
username
roles {
description
}
}
}
GraphQL engine correctly ignores duplicates, (deeply) merges etc. queried fields on execution, but it is not reflected in the info argument. When you add unions and inline fragments it just gets hairier.
Is there a way to construct a collection of all fields requested in a query, taking in account advanced querying capabilities of GraphQL?
Info about the info argument can be found on the Apollo docs site and in the graphql-js Github repo.
I know it has been a while but in case anyone ends up here, there is an npm package called graphql-list-fields by Jake Pusareti that does this. It handles fragments and skip and include directives.
you can also check the code here.
The result returned from an Apollo mutation is typically a type or a subset of fields from a type, and this is usually great. So my mutation:
const addUserMutation = gql`
mutation createUser($email: String!, $permissions: [CreatePermissionInput]) {
createUser(input: {email: $email, permissions: $permissions}) {
id
created
lastUpdated
uid
email
permissions {
who
...
}
}
}
`
Which is calling:
extend type Mutation {
createUser(input: CreateUserInput!): User
}
Will return the user with the fields listed.
Problem is, I want to know if the user that we just tried to create already existed or not. So how can I edit the response to include this flag? Can you have a mutation return, say, an object like:
{
exists: true,
user: { ... }
}
So I can do this:
this.props.submit({
variables: {
email,
permissions,
},
}).then(({ result }) => {
console.log(result)
// > { exists: true, user: [USER OBJECT] }
})
I get that this will break the auto cache update but sometimes you need the response from an update to tell you more.
Create an additional type for the return result of mutation
type UserPayLoad {
exists:Boolean
user:User
}
extend type Mutation {
createUser(input: CreateUserInput!): UserPayLoad
}
Just try this. This may help you