Amplify CLI Version
12.10.2
Question
Hi everyone,
I'm using Amplify Gen 1 to define my AppSync schema, and I have fields with @hasOne relationships. My goal is to create a Global Secondary Index (GSI) on the underlying DynamoDB table based on the field targeted by the @hasOne relationship.
However, my understanding is that Amplify Gen 1 directives don't directly support adding an index (like @index) to a field that also has a relationship directive like @hasOne.
Because of this limitation, I manually created the required GSI on the relevant DynamoDB table using the AWS Console.
Now, I'm trying to define a custom query in AppSync that utilizes this manually created GSI. Here's what I've attempted and the issues I encountered:
Attempt 1: Defining the Custom Query in schema.graphql
-
Context: I want to add a GSI related to the posts field on the Blog type. Since I can't add @index directly alongside @hasOne, I manually created the GSI on the BlogTable in DynamoDB.
# Original Schema Snippet
type Blog @model @auth(rules: [{ allow: private }, { allow: private, provider: iam }]) {
id: ID!
name: String!
content: String!
posts: [Group] @hasOne # Need GSI related to the underlying key for this relationship
}
type Group @model @auth(rules: [{ allow: private }, { allow: private, provider: iam }]) {
id: ID!
name: String!
}
-
Added Custom Query Definition to schema.graphql:
# Added Custom Query Types and Definition
input ListBlogsByGroupIdInput {
blogGroupGroupId: ID # Assuming this is the GSI key name I created manually
}
type ListBlogsByGroupIdOutput @aws_iam @aws_cognito_user_pools {
id: ID!
name: String!
content: String!
# Potentially other fields from Blog
}
type Query {
# Added custom query
listBlogsByGroupId(input: ListBlogsByGroupIdInput): [ListBlogsByGroupIdOutput]
@auth(rules: [{ allow: private }, { allow: private, provider: iam }])
}
-
Result: When I deploy this schema, Amplify automatically generates a resolver for listBlogsByGroupId. However, this auto-generated resolver has its data source set to NONE_DS, and the request mapping template is a basic one focused only on authorization, ultimately returning an empty payload:
## [Start] Field Authorization Steps. **
#set( $isAuthorized = false )
#if( $util.authType() == "IAM Authorization" )
#foreach( $adminRole in $ctx.stash.adminRoles )
#if( $ctx.identity.userArn.contains($adminRole) && $ctx.identity.userArn != $ctx.stash.authRole && $ctx.identity.userArn != $ctx.stash.unauthRole )
#return($context.source.listBlogsByGroupId) # Corrected hypothetical return path
#end
#end
#if( !$isAuthorized )
#if( ($ctx.identity.userArn == $ctx.stash.authRole) || ($ctx.identity.cognitoIdentityPoolId == $ctx.stash.identityPoolId && $ctx.identity.cognitoIdentityAuthType == "authenticated") )
#set( $isAuthorized = true )
#end
#end
#end
#if( $util.authType() == "User Pool Authorization" )
#set( $isAuthorized = true )
#end
#if( !$isAuthorized )
$util.unauthorized()
#end
$util.toJson({"version":"2018-05-29","payload":{}})
## [End] Field Authorization Steps. **
Consequently, invoking the listBlogsByGroupId query always returns null.
Attempt 2: Using a Custom CDK Stack to Define the Resolver
(Note: I decided against using a Lambda resolver due to potential performance implications.)
-
Approach: Based on the Amplify documentation for overriding resolvers (https://docs.amplify.aws/gen1/angular/build-a-backend/graphqlapi/custom-business-logic/#override-amplify-generated-resolvers), it seems the process involves modifying schema.graphql and creating a custom CDK stack to define the resolver logic.
-
Implementation: I kept the schema.graphql changes from Attempt 1 and created a custom CDK stack (/amplify/backend/api/<api-name>/stacks/cdk-stack.ts) to define the resolver for listBlogsByGroupId, pointing it to the correct DynamoDB table (BlogTable) and using the AppSync JS runtime (code adapted from Unit Resolver examples):
import * as cdk from 'aws-cdk-lib';
import * as AmplifyHelpers from '@aws-amplify/cli-extensibility-helper';
import * as appsync from 'aws-cdk-lib/aws-appsync';
import { AmplifyDependentResourcesAttributes } from '../../types/amplify-dependent-resources-ref';
import { Construct } from 'constructs';
// Basic JS request/response template (needs actual GSI query logic)
const jsResolverTemplate = `
import { util } from '@aws-appsync/utils';
export function request(ctx) {
// TODO: Replace with actual GSI query logic
// Example structure for querying GSI:
// return {
// operation: 'Query',
// index: 'your-gsi-name', // Replace with your actual GSI name
// query: {
// expression: '#key = :value',
// expressionNames: {
// '#key': 'blogGroupGroupId' // Replace with your GSI partition key name
// },
// expressionValues: {
// ':value': util.dynamodb.toDynamoDB(ctx.args.input.blogGroupGroupId)
// }
// },
// // Add limit, nextToken etc. as needed
// };
console.log('Placeholder request resolver');
return { operation: 'Scan' }; // Placeholder - THIS IS WRONG for GSI
}
export function response(ctx) {
// TODO: Process results from GSI query
if (ctx.error) {
util.error(ctx.error.message, ctx.error.type);
}
return ctx.result.items; // Assuming query returns items
}
`;
export class cdkStack extends cdk.Stack {
constructor(
scope: Construct,
id: string,
props?: cdk.StackProps,
amplifyResourceProps?: AmplifyHelpers.AmplifyResourceProps
) {
super(scope, id, props);
/* Do not remove - Amplify CLI automatically injects the current deployment environment in this input parameter */
new cdk.CfnParameter(this, 'env', {
type: 'String',
description: 'Current Amplify CLI env name'
});
const retVal: AmplifyDependentResourcesAttributes =
AmplifyHelpers.addResourceDependency(
this,
amplifyResourceProps.category,
amplifyResourceProps.resourceName,
[
{
category: 'api',
resourceName: 'testApi' // Replace with your actual API name
}
]
);
// Define the JS resolver
new appsync.Resolver(this, 'ListBlogsByGroupIdResolver', {
apiId: cdk.Fn.ref(retVal.api.testApi.GraphQLAPIIdOutput),
fieldName: 'listBlogsByGroupId',
typeName: 'Query', // Query | Mutation | Subscription
code: jsResolverTemplate,
dataSourceName: 'ProjectTable', // DataSource name
runtime: {
name: 'APPSYNC_JS',
runtimeVersion: '1.0.0'
}
});
}
}
-
Result: When I try to deploy both the schema.graphql containing the custom query and this custom CDK stack, the deployment fails with an error like:
Resource handler returned message: "No field named listBlogsByGroupId found on type Query (Service: AppSync, Status Code: 404, Request ID: xxxx) (SDK Attempt Count: 1)" (RequestToken: xxxx, HandlerErrorCode: NotFound)
This error suggests either:
- Amplify is trying to create the resolver based on the schema before the schema definition is fully registered or recognized.
- There's a conflict because both the schema definition implicitly asks for a resolver and the CDK explicitly defines one.
Question:
What is the correct way within the Amplify Gen 1 framework to define a custom AppSync query (listBlogsByGroupId) that utilizes a manually created GSI on a DynamoDB table, especially when the GSI relates to a field managed by @hasOne? How can I define both the query in the schema and its corresponding resolver (pointing to the correct DynamoDB data source and using the GSI) without causing deployment conflicts or the NONE_DS issue?
Thanks for any guidance!
Amplify CLI Version
12.10.2
Question
Hi everyone,
I'm using Amplify Gen 1 to define my AppSync schema, and I have fields with
@hasOnerelationships. My goal is to create a Global Secondary Index (GSI) on the underlying DynamoDB table based on the field targeted by the@hasOnerelationship.However, my understanding is that Amplify Gen 1 directives don't directly support adding an index (like
@index) to a field that also has a relationship directive like@hasOne.Because of this limitation, I manually created the required GSI on the relevant DynamoDB table using the AWS Console.
Now, I'm trying to define a custom query in AppSync that utilizes this manually created GSI. Here's what I've attempted and the issues I encountered:
Attempt 1: Defining the Custom Query in
schema.graphqlContext: I want to add a GSI related to the
postsfield on theBlogtype. Since I can't add@indexdirectly alongside@hasOne, I manually created the GSI on theBlogTablein DynamoDB.Added Custom Query Definition to
schema.graphql:Result: When I deploy this schema, Amplify automatically generates a resolver for
listBlogsByGroupId. However, this auto-generated resolver has its data source set toNONE_DS, and the request mapping template is a basic one focused only on authorization, ultimately returning an empty payload:Consequently, invoking the
listBlogsByGroupIdquery always returnsnull.Attempt 2: Using a Custom CDK Stack to Define the Resolver
(Note: I decided against using a Lambda resolver due to potential performance implications.)
Approach: Based on the Amplify documentation for overriding resolvers (https://docs.amplify.aws/gen1/angular/build-a-backend/graphqlapi/custom-business-logic/#override-amplify-generated-resolvers), it seems the process involves modifying
schema.graphqland creating a custom CDK stack to define the resolver logic.Implementation: I kept the
schema.graphqlchanges from Attempt 1 and created a custom CDK stack (/amplify/backend/api/<api-name>/stacks/cdk-stack.ts) to define the resolver forlistBlogsByGroupId, pointing it to the correct DynamoDB table (BlogTable) and using the AppSync JS runtime (code adapted from Unit Resolver examples):Result: When I try to deploy both the
schema.graphqlcontaining the custom query and this custom CDK stack, the deployment fails with an error like:Resource handler returned message: "No field named listBlogsByGroupId found on type Query (Service: AppSync, Status Code: 404, Request ID: xxxx) (SDK Attempt Count: 1)" (RequestToken: xxxx, HandlerErrorCode: NotFound)This error suggests either:
Question:
What is the correct way within the Amplify Gen 1 framework to define a custom AppSync query (
listBlogsByGroupId) that utilizes a manually created GSI on a DynamoDB table, especially when the GSI relates to a field managed by@hasOne? How can I define both the query in the schema and its corresponding resolver (pointing to the correct DynamoDB data source and using the GSI) without causing deployment conflicts or theNONE_DSissue?Thanks for any guidance!