question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

(@aws-cdk/aws-appsync-alpha): Missing AppSync Resolvers

See original GitHub issue

General 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:closed
  • Created 2 years ago
  • Comments:11 (3 by maintainers)

github_iconTop GitHub Comments

2reactions
thantoscommented, May 2, 2022

Figured it out:

https://cs.github.com/aws/aws-cdk/blob/ca3920dbd588ebd9c68f17bfbf420713cf42790a/packages/@aws-cdk/aws-appsync/lib/schema-intermediate.ts#L174

If your linkd package and application package use different instances of @aws-cdk/aws-appsync-alpha the if (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

// @ts-ignore
!obj.resolvers && obj.generateResolver(api2, "hi3", field.fieldOptions);

Longer term would probably update ResolvableField to use a symbol.

0reactions
bestickleycommented, May 3, 2022

Oh interesting. Definitely open up another issue if you experience the issue again.

Read more comments on GitHub >

github_iconTop 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 >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found