I am trying to write a resolver for related entities.
Here is how my schema looks.
type User{
id:ID!
name: String!
posts:[Post] #Resolver 1
}
type Post{
id:ID!,
title: String!
body: String!
}
type CreatePostInput{
id:ID!,
title: String!
body: String!
}
type mutation{
addUserPost(userid:ID!, input:CreatePostInput!): Post
}
Now I added a resolver for posts (see #resolver 1) as
{
"version" : "2017-02-28",
"operation" : "Scan",
"key": {
"userid" : { "S" : "${context.source.id}" }
},
}
Now I added a resolver for the mutation addUserPost as
{
"version" : "2017-02-28",
"operation" : "PutItem",
"key": {
"userid" : { "S" : "${context.arguments.userid}" },
"input" : $util.dynamodb.toDynamoDBJson(${context.arguments.input})
}
}
Now when i run the query
mutation addnewposttest{
addChapterToCourse(addUserPost:"c85a0508-ee0e-4ad8-8629-34880e1c6d74",
input:{
title:"Demo",
body:"Test Body",
id: "c85a0508-c85a0-508c-85a-0508"
}){
id
}
}
I get DynamoDB:AmazonDynamoDBException as One or more parameter values were invalid: Missing the key id in the item (Service: AmazonDynamoDBv2; Status Code: 400; Error Code: ValidationException; Request ID: XXXXXXXXXXXX
I tried changing the data source for the second resolver, but with no luck. I did not find any good documentation form AWS except for this , but this talks about simple string data type, not for object type collection.
Can someone help me understand, how to deal with relationships in resolvers? Thanks
I was referring to two separate data tables and to store the data in two tables I need to use BatchUpdate item and specify the table names. However, I could not do it as in my usecase, i had to insert in one table and update in another table.
Finally, I ended up copying the Id in another collection and building an index on it.
This is how my final schema looked
type User{
id:ID!
name: String!
posts:[Post] #Resolver 1 -
}
type Post{
id:ID!,
userId:ID!# I will look for source.id on this field
title: String!
body: String!
}
Thanks everyone for pitching in!
As I commented, you have some update to make.
But the main problem is that your resolver template for addUserPost contains userid but it looks like you need to change it to id. It looks like your User type or the Post type don't have a field named userid
Related
Implementing subscriptions for AWS AppSync I use the enhanced filter capability to filter out tasks, that does not belong to a specific user.
To distinguish between users an ID is used in the claims part of the verified JWT that is then parsed in the $context object in the VTL response mapping.
But subscribers will always receive all objects that are created without the filter taking effect.
Our graphql schema (simplified) is looking like this
type Mutation {
createTask(
done: Boolean!,
due: String!,
id: String!,
identityId: String!,
read: Boolean!,
note: String!,
): Task
}
type Subscription {
create: Task
#aws_subscribe(mutations: ["createTask"])
}
type Task #aws_iam
#aws_oidc {
identityId: String!
done: Boolean
due: String
id: String
read: Boolean
note: String
}
The datasource for the subscription resolver is a NONE datasource and the request and response mappings are the following:
Request:
{
"version": "2017-02-28"
}
Response:
$extensions.setSubscriptionFilter({
"filterGroup": [
{
"filters" : [
{
"fieldName" : "identityId",
"operator" : "eq",
"value" : $context.identity.claims.identityId
}
]
}
]
})
$util.toJson($context.result)
With this enhanced filter I expect AppSync to filter out all tasks where the identityId does not match the one in the token... but that does not work for any reason.
What do i miss?
After a long search and almost giving up, I found the solution myself.
It's all about the correct composition of the payload attribute in the request mapping.
Without the payload object one could not access the claims in the identity part of the context object. Or at least the filtering doesn't seem to work.
Finally my request mapping looks like this:
{
"version" : "2017-02-28",
"payload" : {
"resultList" : $util.toJson($context.result),
"idnId" : "$context.identity.claims.identityId"
}
}
And in the response mapping
$extensions.setSubscriptionFilter({
"filterGroup": [{
"filters" : [{
"fieldName" : "identityId",
"operator" : "eq",
"value" : $context.result.idnId
}]
}]
})
$util.toJson($context.result.resultList)
I can then access the two objects.
So the filtering now works as expected.
Thank you for checking out my question. My case is the following:
I am trying to query (BatchGetItem) a table by the key "clientId", which is also a secondary index. However the response of the server is The provided key element does not match the schema (Service: DynamoDb, Status Code: 400, Request ID: ..., Extended Request ID: null). I used Amazon tutorial-dynamodb-batch as example and I thought I was able to get items based on a field of choice as in the batchgetitem-retrieve-readings example.
My request.vtl code is:
#set($clientPermissions = $ctx.stash.clientPermissions)
#if( $util.isList($clientPermissions) )
#set($ids = [])
#foreach($item in $clientPermissions)
#set($map = {})
$util.qr($map.put("clientId", $util.dynamodb.toString($item.clientId)))
$util.qr($ids.add($map))
#end
{
"version" : "2018-05-29",
"operation" : "BatchGetItem",
"tables" : {
"Organization-u2zvdkpmrvbovku7cnyivjcaqq-develop": {
"keys": $util.toJson($ids),
"consistentRead": true
}
}
}
#else
#return
#end
This is my table schema:
##
## Organizations
##
type Organization
#model(subscriptions: null)
##auth(rules: [ { allow: groups, groups: ["Admin"] } ])
#key(name: "byClient", fields: ["clientId"])
{
id: ID!
name: String!
parents: [OrganizationRelation] #connection(keyName: "byChild", fields: ["id"])
children: [OrganizationRelation] #connection(keyName: "byParent", fields: ["id"])
clientId: ID!
client: Client #connection(fields: ["clientId"])
}
Hope someone has any experience with this. I am really stuck if I can't fix this.
Thank you in advance.
Best regards,
Jens
I'm new to AppSync and trying to see how this works and what's the proper way to set this up.
I created schema.graphql looks like below.
type User #model {
id: String!
following: [String]
follower: [String]
journals: [Journal] #connection(name: "UserJournals", sortField: "createdAt")
notifications: [Notification] #connection(name: "UserNotifications", sortField: "createdAt")
}
type Journal #model {
id: ID!
author: User! #connection(name: "UserJournals")
privacy: String!
content: AWSJSON!
loved: [String]
createdAt: String
updatedAt: String
}
and this created queries.js automatically by AppSync.
export const getUser = `query GetUser($id: ID!) {
getUser(id: $id) {
id
following
follower
journals {
items {
id
privacy
content
loved
createdAt
updatedAt
}
nextToken
}
notifications {
items {
id
content
category
link
createdAt
}
nextToken
}
}
}
`;
I noticed that querying getUser only returns 10 journals items and not sure how to set that to more than 10 or proper way to query and add more journals into that 10 items that were queried by getUser.
Since you do not pass the limit argument explicitly in your query, the Request Mapping Template of the journals resolver defaults it to 10 items. If you would like to change this default value, go to your schema page on the AppSync console, navigate to the journals field, found under the Resolvers section of the schema page. This will then show the resolver definition for this field, and you can then update the default value of 10 to anything you like. Alternatively, you can pass this as your query argument.
FYI - This default value is defined in the amplify-cli repo on GitHub and can be found here.
DynamoDB operates best with a single table per application (https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/bp-general-nosql-design.html), yet AppSync by default breaks that rule by the way it auto-generates code from the GraphQL schema (that AWS recommends users allow the API to do). Therefore, to use AppSync with GraphQL while upholding DynamoDB's best practices (assuming DynamoDB is the sole data source for the GraphQL API), would this approach work?
First, create a blank DynamoDB table (TheTable in this example) and give it a partition key (partitionKey) and a sort key (sortKey).
Second, manually enforce every GraphQL type to be backed by that table (TheTable). This is where AppSync automatic code generation will go the other direction.
GraphQL schema:
type Pineapple {
partitionKey: String!
sortKey: String!
name: String!
}
# create varying types as long as they all map to the same table
type MachineGun {
partitionKey: String!
sortKey: String!
name: String!
}
input CreatePineappleInput {
partitionKey: String!
sortKey: String!
name: String!
}
type Mutation {
createPineapple(input: CreatePineappleInput!): Pineapple
}
Third, configure your own resolvers to handle the schema (again avoid auto-generated code):
Resolver:
{
"version" : "2017-02-28",
"operation" : "PutItem",
"key" : {
"partitionKey": $util.dynamodb.toDynamoDBJson($ctx.args.input.partitionKey),
"sortKey": $util.dynamodb.toDynamoDBJson($ctx.args.input.sortKey),
},
"attributeValues" : $util.dynamodb.toMapValuesJson($ctx.args.input),
}
And when we run the mutation in the AppSync console:
GraphQL operation:
mutation createPineapple($createPineappleInput: CreatePineappleInput!) {
createPineapple(input: $createPineappleInput) {
name
}
}
{
"createPineappleInput": {
"partitionKey": "attraction123",
"sortKey": "meta",
"name": "Looking OK"
}
}
We get the result we hoped for:
{
"data": {
"createPineapple": {
"name": "Looking OK"
}
}
}
Is there a reason why this wouldn't achieve single-table efficiency using AppSync?
I'm not sure this statement is true
DynamoDB operates best with a single table per application
Do you mind sharing where you saw this?
DynamoDB does indeed work best if the table schema is built based on the application access patterns. That does not necessarily mean you must fit everything in one table.
I have a problem with AWS AppSync and ApolloClient.
How can I use an association between users in the Amazon Service named AppSync, that is, a connection as node and edge. What I want to do is when I follow the users, I would like to see the flow of all users with a single request.
It is the request that I want to be. How do I build a structure for this?
query {
getFeeds(id:"myUserId") {
following {
userFeed {
id
ImageDataUrl
textData
date
}
}
}
}
The schema I created is as follows
type Comments {
id: ID!
date: Int!
message: String!
user: User
}
type Feed {
id: ID!
user: User!
date: Int!
textData: String
ImageDataUrl: String
VideoDataUrl: String
likes: Like
comments: [Comments]
}
#Objects
type Like {
id: ID!
number: Int!
likers: [User]
}
}
type Query {
getAllUsers(limit: Int): [User]
}
type User {
id: ID!
name: String!
email: String!
imageUrl: String!
imageThumbUrl: String!
followers: [User]
following: [User]
userFeed: [Feed]
}
schema {
query: Query
}
This is possible in AppSync today.
To accomplish this, you could add a query field to your schema called getUser (getUser makes more sense than getFeeds in this case) and it would have a resolver which retrieves a User object from a data source.
type Query {
getAllUsers(limit: Int): [User]
getUser(id:ID!): User
}
Then, you can also add resolvers on the User.following and User.userFeed fields. The User.following resolver would query your data source and retrieve users whom somebody is following. The User.userFeed resolver would query your data source to retrieve a list of user feeds.
Both of these resolvers (User.following and User.userFeed) should utilize $context.source in the resolver's request mapping template. This variable will contain the result of your getUser resolver. The request mapping template's job is to create a query which your data source understands.
An example request mapping template which might be attached to User.following could be similar to the following. It would query a table named "Following", which has a primary partition key of id (the id of the user):
{
"version" : "2017-02-28",
"operation" : "Query",
"query" : {
## Provide a query expression. **
"expression": "id = :id",
"expressionValues" : {
":id" : {
## Use the result of getUser to populate the query parameter **
"S" : "${ctx.source.id}"
}
}
}
}
You would have to do something similar for User.userFeed resolver.
After you're all setup, you can run the below query, and the following will happen:
query {
getUser(id:"myUserId") {
following {
userFeed {
id
ImageDataUrl
textData
date
}
}
}
}
getUser resolver will run first. It will query your User data source and retrieve the user.
User.following resolver will run. It will use the result of it's parent field resolver (getUser) to query the data source for following.
User.userFeed resolver will run. It will use the result of it's parent field resolver (getUser) to query the user feed data source.