* Which Category is your question related to? *
API Schema
* What AWS Services are you utilizing? *
AppSync
* Provide additional details e.g. code snippets *
I have schema and sample relation
type Menu @model {
id: ID!
date: AWSDate!
title: String!
products: [Product]! @connection(name: "ProductsMenu")
}
each menu has many products
but the mutation not give me ability to insert products,
the input of mutation looks like this, No products
input CreateMenuInput {
id: ID
date: AWSDate!
title: String!
}
I expect I my mutation will be like this
mutation addMenu {
createMenu(input: {
title:"menu 1"
date:"2019-04-25"
products: ["productID"]
}){
title
}
}
GraphQL transfromer does not directly adding an item in connection when its Many to one relation. The way to add this is to first CreateMenu
and then add items individually by using CreateProduct
.
To support the this use case to create records in multiple Dynamo DB tables you will have to create an custom mutatation and use pipeline resolvers.
my schema
type Product @model @searchable {
id: ID!
name: String!
description: String!
image: S3Object!
category: Category
price: Float!
}
type Menu @model {
id: ID!
date: AWSDate!
products: [Product]! @connection
}
input FullMenuInput {
date: AWSDate!
products: [ID]!
}
type Mutation {
createFullMenu(input: FullMenuInput): Menu
}
stacks resolver
"MutationCreateFullMenuResolver": {
"Type": "AWS::AppSync::Resolver",
"Properties": {
"ApiId": {
"Ref": "AppSyncApiId"
},
"DataSourceName": "MenuTable",
"TypeName": "Mutation",
"FieldName": "createFullMenu",
"RequestMappingTemplateS3Location": {
"Fn::Sub": [
"s3://${S3DeploymentBucket}/${S3DeploymentRootKey}/resolvers/Mutation.createFullMenu.req.vtl",
{
"S3DeploymentBucket": {
"Ref": "S3DeploymentBucket"
},
"S3DeploymentRootKey": {
"Ref": "S3DeploymentRootKey"
}
}
]
},
"ResponseMappingTemplateS3Location": {
"Fn::Sub": [
"s3://${S3DeploymentBucket}/${S3DeploymentRootKey}/resolvers/Mutation.createFullMenu.res.vtl",
{
"S3DeploymentBucket": {
"Ref": "S3DeploymentBucket"
},
"S3DeploymentRootKey": {
"Ref": "S3DeploymentRootKey"
}
}
]
}
}
}
## Mutation.createFullMenu.req.vtl **
#set( $limit = $util.defaultIfNull($context.args.limit, 10) )
{
"version": "2017-02-28",
"operation": "Mutation",
"query": {
"expression": "#connectionAttribute = :connectionAttribute",
"expressionNames": {
"#connectionAttribute": "productsId"
},
"expressionValues": {
":connectionAttribute": {
"S": "$context.args.date"
}
}
},
"scanIndexForward": true,
"limit": $limit,
"nextToken": #if( $context.args.nextToken ) "$context.args.nextToken" #else null #end
}
## Mutation.createFullMenu.res.vtl **
$util.toJson($ctx.result)
what is the wrong with this ?
@NuruddinBadawi how are you associating Product
to Menu
when using createFullMenu
? FullMenuInput
has just date
and Products
. How would you decide what product is in which menu?
In AppSync an Unit resolver (non pipeline one) can have only single data source.If you want to update multiple table in single mutation you have 2 options (both are not directly supported in GraphQL transformer)
Menu
and then adds Product
in another AppSync function.I will mark this as a feature request and put it in our backlog for product team to prioritize this
thanks for your response, maybe my schema is wrong, but what I'm thinking about is like this:
I don't need to know what product is in which menu, just I want to know what the products inside single menu "menu mean ==> food menu",
so for this I create unnamed @connection.
correct me if i'm wrong.
The menu should contain products Id's
I don't know how to create Pipeline Resolvers, I try it but it's hard to understand,
can you add to me some code snippet to guid me.
@NuruddinBadawi If you want to create a menu all at once then you can use a single Menu @model with neseted object types. For example:
type Menu @model {
id: ID!
date: String!
products: [Product]
}
type Product { # note that this is not a model.
id: ID!
name: String!
description: String!
image: S3Object!
category: Category
price: Float!
}
This will store all your menu information in a single table where the products attribute is a list of map values. I think this pattern will work better for your use case and will allow you to create a menu at once via the auto-generated Mutation.createMenu
mutation.
@mikeparisstuff Thank you for your great suggestion.
So now to create queries and mutations for the product should I do it my self like this ? or what?
type Query {
getProduct(id: ID!): Product
listProducts: [Product]
}
type Mutation {
createProduct
updateProduct
}
@mikeparisstuff can you tell me how this modal work if I need Product table to make with it mutations and queries?
If you need both Product & Menu to be top level types then you should be creating a menu first and then attaching products to the menu with independent mutations. With this schema:
type Product @model @searchable {
id: ID!
name: String!
description: String!
image: S3Object!
category: Category
price: Float!
menu: Menu @connection(name: "MenuProducts")
}
type Menu @model {
id: ID!
date: AWSDate!
products: [Product]! @connection(name: "MenuProducts")
}
First create the menu:
mutation addMenu {
createMenu(input: {
id: "menu1"
title:"menu 1"
date:"2019-04-25"
}){
title
}
}
and then add products to the menu:
mutation addProduct {
createProduct(input: {
name:"product 1",
productMenuId: "menu1"
// ... description, s3Image etc.
}){
name
}
}
If the product already exists then use the update mutation instead to set the productMenuId. If you want to batch multiple associations at once you can use query field aliasing and multiple mutation fields.
mutation {
cp1: createProduct(input: {
name:"product 1",
productMenuId: "menu1"
// ... description, s3Image etc.
}){
name
}
cp2: createProduct(input: {
name:"product 1",
productMenuId: "menu1"
// ... description, s3Image etc.
}){
name
}
...
}
thank you @mikeparisstuff for your effort,
last question:
can you give me example if I need to allow to product to be in many menu? I think I need to use many-to-many relation.
but if I can't use
menus: [Menu] @connection(name: "MenuProducts")
and also
products: [Product]! @connection(name: "MenuProducts")
together what should I do with this case ?
type Product @model @searchable {
id: ID!
name: String!
description: String!
image: S3Object!
category: Category
price: Float!
menu: Menu @connection(name: "ProductProductMenu")
}
type Menu @model {
id: ID!
date: AWSDate!
products: [Product]! @connection(name: "MenuProductMenu")
}
type ProductMenu @model {
id: ID!
date: AWSDate!
menus: [Menu]! @connection(name: "MenuProductMenu")
products: [Product]! @connection(name: "ProductProductMenu")
}
You would nest with an extra layer as above. Then you mutate always on ProductMenu to link the two just as you would in any standard SQL Database.
Not sure if there is any other plan to automatically handle many to many relations in an easier way - but at least that's what happens in the background.
Most helpful comment
@NuruddinBadawi If you want to create a menu all at once then you can use a single Menu @model with neseted object types. For example:
This will store all your menu information in a single table where the products attribute is a list of map values. I think this pattern will work better for your use case and will allow you to create a menu at once via the auto-generated
Mutation.createMenu
mutation.