(@aws-cdk/aws-appsync-alpha): Missing AppSync Resolvers
See original GitHub issueGeneral Issue
Missing AppSync Resolvers
The Question
The following CDK construct follows the documentation in how to create a GQL API but the resolvers are not being created:
import { Arn, Stack } from "aws-cdk-lib";
import { PolicyStatement } from "aws-cdk-lib/aws-iam";
import { Construct } from "constructs";
import { GraphqlApi } from "@aws-cdk/aws-appsync-alpha";
import { Function } from "../function.js";
import { createSchema } from "./createSchema.js";
import { Stage } from "../stages.js";
export interface UserManagementProps {
api: GraphqlApi;
userPoolId: string;
groupNames: string[];
/**
* Admin Group Names
*
* Admin group names allocate elevated privileges to users in these groups
*/
adminGroupNames: string[];
stage: Stage;
}
/**
* UserManagement Construct
* Creates an Cognito User Pool, Groups, Client
*/
export class UserManagement extends Construct {
constructor(scope: Construct, id: string, props: UserManagementProps) {
super(scope, id);
const { api, groupNames, adminGroupNames, userPoolId, stage } = props;
const userFn = new Function(this, "UserFunction", {
entry: new URL("./function/index.ts", import.meta.url).pathname,
environment: {
USER_POOL_ID: userPoolId,
GROUP_NAMES: JSON.stringify(groupNames),
ADMIN_GROUP_NAMES: JSON.stringify(adminGroupNames),
},
initialPolicy: [
new PolicyStatement({
actions: [
"cognito-idp:AdminAddUserToGroup",
"cognito-idp:AdminCreateUser",
"cognito-idp:AdminDeleteUser",
"cognito-idp:AdminDisableUser",
"cognito-idp:AdminEnableUser",
"cognito-idp:AdminGetUser",
"cognito-idp:AdminListGroupsForUser",
"cognito-idp:AdminListUsersInGroup",
"cognito-idp:AdminRemoveUserFromGroup",
"cognito-idp:AdminResetUserPassword",
"cognito-idp:AdminUpdateUserAttributes",
"cognito-idp:ListGroups",
"cognito-idp:ListUsers",
"cognito-idp:ListUsersInGroup",
],
resources: [
Arn.format(
{
service: "cognito-idp",
resource: `userpool/${userPoolId}`,
},
Stack.of(this)
),
],
}),
],
memorySize: 256,
stage,
});
const userDs = api.addLambdaDataSource("UserFn", userFn);
createSchema(api, userDs);
}
}
import {
GraphqlApi,
GraphqlType,
InputType,
ObjectType,
ResolvableField,
BaseDataSource,
EnumType,
Directive,
MappingTemplate,
} from "@aws-cdk/aws-appsync-alpha";
import { groupNames, adminGroupNames } from "./function/group.js";
export function createSchema(api: GraphqlApi, dataSource: BaseDataSource) {
// Enum Types
const groupNameEnum = new EnumType("GroupNameEnum", {
definition: groupNames,
});
api.addType(groupNameEnum);
// Input Types
const createUserInput = new InputType("CreateUserInput", {
definition: {
email: GraphqlType.awsEmail({ isRequired: true }),
family_name: GraphqlType.string({ isRequired: true }),
given_name: GraphqlType.string({ isRequired: true }),
groups: groupNameEnum.attribute({
isList: true,
isRequired: true,
isRequiredList: true,
}),
username: GraphqlType.string({ isRequired: true }),
},
});
api.addType(createUserInput);
const filterInput = new InputType("FilterInput", {
definition: {
field: GraphqlType.string({ isRequired: true }),
operator: GraphqlType.string({ isRequired: true }),
value: GraphqlType.string({ isRequired: true }),
},
});
api.addType(filterInput);
const listUsersInput = new InputType("ListUsersInput", {
definition: {
limit: GraphqlType.int(),
nextToken: GraphqlType.string(),
filterInput: filterInput.attribute(),
},
});
api.addType(listUsersInput);
const listUsersInGroupInput = new InputType("ListUsersInGroupInput", {
definition: {
groupName: GraphqlType.string({ isRequired: true }),
limit: GraphqlType.int(),
nextToken: GraphqlType.string(),
},
});
api.addType(listUsersInGroupInput);
const updateUserInput = new InputType("UpdateUserInput", {
definition: {
email: GraphqlType.awsEmail(),
family_name: GraphqlType.string(),
given_name: GraphqlType.string(),
groups: groupNameEnum.attribute(),
username: GraphqlType.string({ isRequired: true }),
},
});
api.addType(updateUserInput);
// Object Types
const userType = new ObjectType("User", {
definition: {
createdAt: GraphqlType.awsDateTime({ isRequired: true }),
email: GraphqlType.awsEmail({ isRequired: true }),
email_verified: GraphqlType.string(),
enabled: GraphqlType.boolean({ isRequired: true }),
given_name: GraphqlType.string({ isRequired: true }),
groups: GraphqlType.string({
isList: true,
isRequiredList: true,
isRequired: true,
}),
family_name: GraphqlType.string({ isRequired: true }),
status: GraphqlType.string({ isRequired: true }),
sub: GraphqlType.id({ isRequired: true }),
updatedAt: GraphqlType.awsDateTime({ isRequired: true }),
username: GraphqlType.string({ isRequired: true }),
},
});
api.addType(userType);
const userConnection = new ObjectType("UserConnection", {
definition: {
users: userType.attribute({ isList: true, isRequiredList: true }),
nextToken: GraphqlType.string(),
},
});
api.addType(userConnection);
const groupType = new ObjectType("Group", {
definition: {
createdAt: GraphqlType.awsDateTime({ isRequired: true }),
description: GraphqlType.string(),
name: groupNameEnum.attribute({ isRequired: true }),
precedence: GraphqlType.int({ isRequired: true }),
updatedAt: GraphqlType.awsDateTime({ isRequired: true }),
},
});
api.addType(groupType);
const groupConnection = new ObjectType("GroupConnection", {
definition: {
groups: groupType.attribute({ isList: true, isRequiredList: true }),
nextToken: GraphqlType.string(),
},
});
api.addType(groupConnection);
// Queries
api.addQuery(
"getUser",
new ResolvableField({
returnType: userType.attribute(),
args: { username: GraphqlType.string({ isRequired: true }) },
dataSource,
requestMappingTemplate: MappingTemplate.lambdaRequest(),
responseMappingTemplate: MappingTemplate.lambdaResult(),
})
);
api.addQuery(
"listGroupsForUser",
new ResolvableField({
returnType: groupType.attribute({
isRequired: true,
isRequiredList: true,
isList: true,
}),
args: { username: GraphqlType.string({ isRequired: true }) },
dataSource,
requestMappingTemplate: MappingTemplate.lambdaRequest(),
responseMappingTemplate: MappingTemplate.lambdaResult(),
})
);
api.addQuery(
"listGroups",
new ResolvableField({
returnType: groupConnection.attribute({ isRequired: true }),
dataSource,
})
);
api.addQuery(
"listUsersInGroup",
new ResolvableField({
returnType: userConnection.attribute({ isRequired: true }),
args: { input: listUsersInGroupInput.attribute() },
dataSource,
requestMappingTemplate: MappingTemplate.lambdaRequest(),
responseMappingTemplate: MappingTemplate.lambdaResult(),
})
);
api.addQuery(
"listUsers",
new ResolvableField({
returnType: userConnection.attribute({ isRequired: true }),
args: { input: listUsersInput.attribute() },
dataSource,
requestMappingTemplate: MappingTemplate.lambdaRequest(),
responseMappingTemplate: MappingTemplate.lambdaResult(),
})
);
// Mutations
api.addMutation(
"createUser",
new ResolvableField({
returnType: userType.attribute({ isRequired: true }),
args: { input: createUserInput.attribute() },
dataSource,
requestMappingTemplate: MappingTemplate.lambdaRequest(),
responseMappingTemplate: MappingTemplate.lambdaResult(),
})
);
api.addMutation(
"deleteUsers",
new ResolvableField({
returnType: GraphqlType.string(),
args: {
usernames: GraphqlType.string({
isRequired: true,
isList: true,
isRequiredList: true,
}),
},
dataSource,
directives: [Directive.cognito(...adminGroupNames)],
requestMappingTemplate: MappingTemplate.lambdaRequest(),
responseMappingTemplate: MappingTemplate.lambdaResult(),
})
);
api.addMutation(
"disableUsers",
new ResolvableField({
returnType: GraphqlType.string(),
args: {
usernames: GraphqlType.string({
isRequired: true,
isList: true,
isRequiredList: true,
}),
},
dataSource,
directives: [Directive.cognito(...adminGroupNames)],
requestMappingTemplate: MappingTemplate.lambdaRequest(),
responseMappingTemplate: MappingTemplate.lambdaResult(),
})
);
api.addMutation(
"enableUsers",
new ResolvableField({
returnType: GraphqlType.string(),
args: {
usernames: GraphqlType.string({
isRequired: true,
isList: true,
isRequiredList: true,
}),
},
dataSource,
directives: [Directive.cognito(...adminGroupNames)],
requestMappingTemplate: MappingTemplate.lambdaRequest(),
responseMappingTemplate: MappingTemplate.lambdaResult(),
})
);
api.addMutation(
"resetPasswords",
new ResolvableField({
returnType: GraphqlType.string(),
args: {
usernames: GraphqlType.string({
isRequired: true,
isList: true,
isRequiredList: true,
}),
},
dataSource,
directives: [Directive.cognito(...adminGroupNames)],
requestMappingTemplate: MappingTemplate.lambdaRequest(),
responseMappingTemplate: MappingTemplate.lambdaResult(),
})
);
api.addMutation(
"updateUser",
new ResolvableField({
returnType: GraphqlType.string(),
args: { input: updateUserInput.attribute() },
dataSource,
directives: [Directive.cognito(...groupNames)],
requestMappingTemplate: MappingTemplate.lambdaRequest(),
responseMappingTemplate: MappingTemplate.lambdaResult(),
})
);
}
Related CloudFormation created. Notice there is no AWS::AppSync:Resolver resource created.
"GqlApi4E487465": {
"Type": "AWS::AppSync::GraphQLApi",
"Properties": {
"AuthenticationType": "AMAZON_COGNITO_USER_POOLS",
"Name": "dev-gql-api",
"UserPoolConfig": {
"AwsRegion": "us-east-1",
"DefaultAction": "ALLOW",
"UserPoolId": {
"Ref": "UserBaseUserPoolC5FEAE0B"
}
}
},
"Metadata": {
"aws:cdk:path": "stickb-back-end/GqlApi/Resource"
}
},
"GqlApiSchema55BCA17B": {
"Type": "AWS::AppSync::GraphQLSchema",
"Properties": {
"ApiId": {
"Fn::GetAtt": [
"GqlApi4E487465",
"ApiId"
]
},
"Definition": "schema {\n query: Query\n mutation: Mutation\n}\ntype Query {\n getUser(username: String!): User\n listGroupsForUser(username: String!): [Group!]!\n listGroups: GroupConnection!\n listUsersInGroup(input: ListUsersInGroupInput): UserConnection!\n listUsers(input: ListUsersInput): UserConnection!\n}\ntype Mutation {\n createUser(input: CreateUserInput): User!\n deleteUsers(usernames: [String!]!): String\n @aws_auth(cognito_groups: [\"Admin\"])\n disableUsers(usernames: [String!]!): String\n @aws_auth(cognito_groups: [\"Admin\"])\n enableUsers(usernames: [String!]!): String\n @aws_auth(cognito_groups: [\"Admin\"])\n resetPasswords(usernames: [String!]!): String\n @aws_auth(cognito_groups: [\"Admin\"])\n updateUser(input: UpdateUserInput): String\n @aws_auth(cognito_groups: [\"Admin\", \"User\", \"UserReadOnly\"])\n}\nenum GroupNameEnum {\n Admin\n User\n UserReadOnly\n}\ninput CreateUserInput {\n email: AWSEmail!\n family_name: String!\n given_name: String!\n groups: [GroupNameEnum!]!\n username: String!\n}\ninput FilterInput {\n field: String!\n operator: String!\n value: String!\n}\ninput ListUsersInput {\n limit: Int\n nextToken: String\n filterInput: FilterInput\n}\ninput ListUsersInGroupInput {\n groupName: String!\n limit: Int\n nextToken: String\n}\ninput UpdateUserInput {\n email: AWSEmail\n family_name: String\n given_name: String\n groups: GroupNameEnum\n username: String!\n}\ntype User {\n createdAt: AWSDateTime!\n email: AWSEmail!\n email_verified: String\n enabled: Boolean!\n given_name: String!\n groups: [String!]!\n family_name: String!\n status: String!\n sub: ID!\n updatedAt: AWSDateTime!\n username: String!\n}\ntype UserConnection {\n users: [User]!\n nextToken: String\n}\ntype Group {\n createdAt: AWSDateTime!\n description: String\n name: GroupNameEnum!\n precedence: Int!\n updatedAt: AWSDateTime!\n}\ntype GroupConnection {\n groups: [Group]!\n nextToken: String\n}\n"
},
"Metadata": {
"aws:cdk:path": "stickb-back-end/GqlApi/Schema"
}
},
"GqlApiUserFnServiceRole14EEC123": {
"Type": "AWS::IAM::Role",
"Properties": {
"AssumeRolePolicyDocument": {
"Statement": [
{
"Action": "sts:AssumeRole",
"Effect": "Allow",
"Principal": {
"Service": "appsync.amazonaws.com"
}
}
],
"Version": "2012-10-17"
}
},
"Metadata": {
"aws:cdk:path": "stickb-back-end/GqlApi/UserFn/ServiceRole/Resource"
}
},
"GqlApiUserFnServiceRoleDefaultPolicy37425C80": {
"Type": "AWS::IAM::Policy",
"Properties": {
"PolicyDocument": {
"Statement": [
{
"Action": "lambda:InvokeFunction",
"Effect": "Allow",
"Resource": {
"Fn::GetAtt": [
"UserManagementUserFunctionEA7B867D",
"Arn"
]
}
}
],
"Version": "2012-10-17"
},
"PolicyName": "GqlApiUserFnServiceRoleDefaultPolicy37425C80",
"Roles": [
{
"Ref": "GqlApiUserFnServiceRole14EEC123"
}
]
},
"Metadata": {
"aws:cdk:path": "stickb-back-end/GqlApi/UserFn/ServiceRole/DefaultPolicy/Resource"
}
},
"GqlApiUserFn61A5880D": {
"Type": "AWS::AppSync::DataSource",
"Properties": {
"ApiId": {
"Fn::GetAtt": [
"GqlApi4E487465",
"ApiId"
]
},
"Name": "UserFn",
"Type": "AWS_LAMBDA",
"LambdaConfig": {
"LambdaFunctionArn": {
"Fn::GetAtt": [
"UserManagementUserFunctionEA7B867D",
"Arn"
]
}
},
"ServiceRoleArn": {
"Fn::GetAtt": [
"GqlApiUserFnServiceRole14EEC123",
"Arn"
]
}
},
"Metadata": {
"aws:cdk:path": "stickb-back-end/GqlApi/UserFn/Resource"
}
},
"UserManagementUserFunctionServiceRoleCFD3905A": {
"Type": "AWS::IAM::Role",
"Properties": {
"AssumeRolePolicyDocument": {
"Statement": [
{
"Action": "sts:AssumeRole",
"Effect": "Allow",
"Principal": {
"Service": "lambda.amazonaws.com"
}
}
],
"Version": "2012-10-17"
},
"ManagedPolicyArns": [
{
"Fn::Join": [
"",
[
"arn:",
{
"Ref": "AWS::Partition"
},
":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
]
]
}
]
},
"Metadata": {
"aws:cdk:path": "stickb-back-end/UserManagement/UserFunction/ServiceRole/Resource"
}
},
"UserManagementUserFunctionServiceRoleDefaultPolicyE34CBBDC": {
"Type": "AWS::IAM::Policy",
"Properties": {
"PolicyDocument": {
"Statement": [
{
"Action": [
"cognito-idp:AdminAddUserToGroup",
"cognito-idp:AdminCreateUser",
"cognito-idp:AdminDeleteUser",
"cognito-idp:AdminDisableUser",
"cognito-idp:AdminEnableUser",
"cognito-idp:AdminGetUser",
"cognito-idp:AdminListGroupsForUser",
"cognito-idp:AdminListUsersInGroup",
"cognito-idp:AdminRemoveUserFromGroup",
"cognito-idp:AdminResetUserPassword",
"cognito-idp:AdminUpdateUserAttributes",
"cognito-idp:ListGroups",
"cognito-idp:ListUsers",
"cognito-idp:ListUsersInGroup"
],
"Effect": "Allow",
"Resource": {
"Fn::Join": [
"",
[
"arn:",
{
"Ref": "AWS::Partition"
},
":cognito-idp:us-east-1:268914465231:userpool/",
{
"Ref": "UserBaseUserPoolC5FEAE0B"
}
]
]
}
}
],
"Version": "2012-10-17"
},
"PolicyName": "UserManagementUserFunctionServiceRoleDefaultPolicyE34CBBDC",
"Roles": [
{
"Ref": "UserManagementUserFunctionServiceRoleCFD3905A"
}
]
},
"Metadata": {
"aws:cdk:path": "stickb-back-end/UserManagement/UserFunction/ServiceRole/DefaultPolicy/Resource"
}
},
"UserManagementUserFunctionEA7B867D": {
"Type": "AWS::Lambda::Function",
"Properties": {
"Code": {
"S3Bucket": "cdk-hnb659fds-assets-268914465231-us-east-1",
"S3Key": "f96f9f3b8614b15e443eccfb1882b609ce1dfbfe8066550d5ca82ba6be46d3bc.zip"
},
"Role": {
"Fn::GetAtt": [
"UserManagementUserFunctionServiceRoleCFD3905A",
"Arn"
]
},
"Environment": {
"Variables": {
"USER_POOL_ID": {
"Ref": "UserBaseUserPoolC5FEAE0B"
},
"GROUP_NAMES": "[\"Admin\",\"User\",\"UserReadOnly\"]",
"ADMIN_GROUP_NAMES": "[\"Admin\"]",
"NODE_OPTIONS": "--enable-source-maps",
"AWS_NODEJS_CONNECTION_REUSE_ENABLED": "1"
}
},
"Handler": "index.handler",
"MemorySize": 256,
"Runtime": "nodejs14.x"
},
"DependsOn": [
"UserManagementUserFunctionServiceRoleDefaultPolicyE34CBBDC",
"UserManagementUserFunctionServiceRoleCFD3905A"
],
"Metadata": {
"aws:cdk:path": "stickb-back-end/UserManagement/UserFunction/Resource",
"aws:asset:path": "asset.f96f9f3b8614b15e443eccfb1882b609ce1dfbfe8066550d5ca82ba6be46d3bc",
"aws:asset:is-bundled": true,
"aws:asset:property": "Code"
}
},
"UserManagementUserFunctionLogRetentionC75D8B7F": {
"Type": "Custom::LogRetention",
"Properties": {
"ServiceToken": {
"Fn::GetAtt": [
"LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aFD4BFC8A",
"Arn"
]
},
"LogGroupName": {
"Fn::Join": [
"",
[
"/aws/lambda/",
{
"Ref": "UserManagementUserFunctionEA7B867D"
}
]
]
},
"RetentionInDays": 30
},
"Metadata": {
"aws:cdk:path": "stickb-back-end/UserManagement/UserFunction/LogRetention/Resource"
}
},
CDK CLI Version
2.5.0 (build 0951122)
Framework Version
2.5.0
Node.js Version
14.18.2
OS
macOS
Language
Typescript
Language Version
4.5.4
Other information
No response
Issue Analytics
- State:
- Created 2 years ago
- Comments:11 (3 by maintainers)
Top Results From Across the Web
Appsync missing resolver - Stack Overflow
AppSync created all the resources and I can create new records with Mutations and that works like a charm. mutation createUsers{ createUsers( ...
Read more >Configuring resolvers (VTL) - AWS AppSync
GraphQL resolvers connect the fields in a type's schema to a data source. Resolvers are the mechanism by which requests are fulfilled. AWS...
Read more >How to Handle Many to Many relations in AppSync
TL;DR; Use a Pipeline resolver to first fetch the relations followed by a BatchGetItem operation to retrieve all related items in one single ......
Read more >Anatomy of an AppSync resolver - Advanced Web Machinery
How request and response mapping work in AppSync ... Resolvers define how to provide the data for a GraphQL field. They provide the...
Read more >Troubleshooting and Common Mistakes - Amazon AppSync
Missing Resolver. If you execute a GraphQL operation, such as a query, and get a null response, this may be because you don't...
Read more >Top Related Medium Post
No results found
Top Related StackOverflow Question
No results found
Troubleshoot Live Code
Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start FreeTop Related Reddit Thread
No results found
Top Related Hackernoon Post
No results found
Top Related Tweet
No results found
Top Related Dev.to Post
No results found
Top Related Hashnode Post
No results found
Top GitHub Comments
Figured it out:
https://cs.github.com/aws/aws-cdk/blob/ca3920dbd588ebd9c68f17bfbf420713cf42790a/packages/@aws-cdk/aws-appsync/lib/schema-intermediate.ts#L174
If your
link
d package and application package use different instances of@aws-cdk/aws-appsync-alpha
theif (field instanceof ResolvableField)
will fail and never generate the resolvers.A quick fix was to run
npm link --only=production [package name]
or force it
Longer term would probably update
ResolvableField
to use a symbol.Oh interesting. Definitely open up another issue if you experience the issue again.