├── .gitignore ├── package.json ├── README.md └── template.yml /.gitignore: -------------------------------------------------------------------------------- 1 | output.yml 2 | .DS_Store 3 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "appsync-graphql-crud-dynamodb", 3 | "version": "1.0.0", 4 | "description": "", 5 | "scripts": { 6 | "package": "aws cloudformation package --template-file template.yml --s3-bucket appsync-graphql-crud-dynamodb --output-template-file output.yml", 7 | "deploy": "aws cloudformation deploy --template-file output.yml --stack-name appsync-graphql-crud-dynamodb --capabilities CAPABILITY_NAMED_IAM", 8 | "qd": "npm run package && npm run deploy" 9 | }, 10 | "author": "Aleksandar Simovic ", 11 | "license": "MIT" 12 | } 13 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # AppSync GraphQL API -> Resolvers -> DynamoDB 3 | 4 | ## Description 5 | 6 | This is a serverless component consisting of: 7 | 8 | - an AppSync GraphQL API with CRUD operations and a single item schema 9 | - CRUD resolvers to save, delete, get one, get all items from a DynamoDB Table 10 | - a DynamoDB table, where all your data is stored. 11 | 12 | Aside from this main functionality, its important features are: 13 | 14 | - Access is provided with an API Key 15 | - No language runtime, everything done through the resolves 16 | - Easily composable into your other app components by adding triggers to its DynamoDB table 17 | 18 | It's a Nuts & Bolts application component for AWS Serverless Application Repository. 19 | 20 | ## Latest Release - 1.0.0 21 | 22 | Initial release. 23 | 24 | ## Roadmap - Upcoming changes 25 | 26 | Here are the upcoming changes that I'll add to this serverless component: 27 | 28 | - ESLint 29 | - Tests 30 | - Conditional DynamoDB table creation -------------------------------------------------------------------------------- /template.yml: -------------------------------------------------------------------------------- 1 | AWSTemplateFormatVersion: 2010-09-09 2 | Transform: AWS::Serverless-2016-10-31 3 | 4 | Parameters: 5 | TableName: 6 | Type: String 7 | Description: (Required) The name of the DynamoDB table you want to create and save to. Minimum 3 characters 8 | MinLength: 3 9 | MaxLength: 50 10 | ConstraintDescription: 'Required parameter. Must be characters and/or underscores. No numbers allowed.' 11 | Default: 'yourDBTableName' 12 | AllowedPattern: ^[A-Za-z_]+$ 13 | StageEnvironment: 14 | Type: String 15 | MinLength: 3 16 | MaxLength: 15 17 | Default: 'dev' 18 | 19 | Resources: 20 | 21 | AppSyncApi: 22 | Type: AWS::AppSync::GraphQLApi 23 | Properties: 24 | Name: !Sub ${TableName}Api${StageEnvironment} 25 | AuthenticationType: API_KEY 26 | 27 | AppSyncAPIKey: 28 | Type: AWS::AppSync::ApiKey 29 | Properties: 30 | ApiId: !GetAtt AppSyncApi.ApiId 31 | 32 | AppSyncSchema: 33 | Type: AWS::AppSync::GraphQLSchema 34 | Properties: 35 | ApiId: !GetAtt AppSyncApi.ApiId 36 | Definition: !Sub | 37 | type ${TableName} { 38 | ${TableName}Id: ID! 39 | name: String 40 | } 41 | type Paginated${TableName} { 42 | items: [${TableName}!]! 43 | nextToken: String 44 | } 45 | type Query { 46 | all(limit: Int, nextToken: String): Paginated${TableName}! 47 | getOne(${TableName}Id: ID!): ${TableName} 48 | } 49 | type Mutation { 50 | save(${TableName}Id: ID!, name: String!): ${TableName} 51 | delete(${TableName}Id: ID!): ${TableName} 52 | } 53 | type Schema { 54 | query: Query 55 | mutation: Mutation 56 | } 57 | 58 | AppSyncTableDataSource: 59 | Type: AWS::AppSync::DataSource 60 | Properties: 61 | ApiId: !GetAtt AppSyncApi.ApiId 62 | Name: !Ref TableName 63 | Description: DynamoDB Table AppSync Data Source 64 | Type: AMAZON_DYNAMODB 65 | ServiceRoleArn: !GetAtt DynamoDBRole.Arn 66 | DynamoDBConfig: 67 | TableName: !Ref DynamoDBTable 68 | AwsRegion: !Sub ${AWS::Region} 69 | 70 | AppSyncGetOneQueryResolver: 71 | Type: AWS::AppSync::Resolver 72 | DependsOn: AppSyncSchema 73 | Properties: 74 | ApiId: !GetAtt AppSyncApi.ApiId 75 | TypeName: Query 76 | FieldName: getOne 77 | DataSourceName: !GetAtt AppSyncTableDataSource.Name 78 | RequestMappingTemplate: !Sub | 79 | { 80 | "version": "2017-02-28", 81 | "operation": "GetItem", 82 | "key": { 83 | "${TableName}Id": $util.dynamodb.toDynamoDBJson($ctx.args.${TableName}Id) 84 | } 85 | } 86 | ResponseMappingTemplate: "$util.toJson($ctx.result)" 87 | 88 | AppSyncAllQueryResolver: 89 | Type: AWS::AppSync::Resolver 90 | DependsOn: AppSyncSchema 91 | Properties: 92 | ApiId: !GetAtt AppSyncApi.ApiId 93 | TypeName: Query 94 | FieldName: all 95 | DataSourceName: !GetAtt AppSyncTableDataSource.Name 96 | RequestMappingTemplate: !Sub | 97 | { 98 | "version": "2017-02-28", 99 | "operation" : "Scan", 100 | "limit": $util.defaultIfNull($ctx.args.limit, 20), 101 | "nextToken": $util.toJson($util.defaultIfNullOrEmpty($ctx.args.nextToken, null)) 102 | }, 103 | ResponseMappingTemplate: "$util.toJson($ctx.result)" 104 | 105 | AppSyncSaveMutationResolver: 106 | Type: AWS::AppSync::Resolver 107 | DependsOn: AppSyncSchema 108 | Properties: 109 | ApiId: !GetAtt AppSyncApi.ApiId 110 | TypeName: Mutation 111 | FieldName: save 112 | DataSourceName: !GetAtt AppSyncTableDataSource.Name 113 | RequestMappingTemplate: !Sub | 114 | { 115 | "version": "2017-02-28", 116 | "operation": "PutItem", 117 | "key": { 118 | "${TableName}Id": $util.dynamodb.toDynamoDBJson($ctx.args.${TableName}Id) 119 | }, 120 | "attributeValues": { 121 | "name": $util.dynamodb.toDynamoDBJson($ctx.args.name) 122 | } 123 | } 124 | ResponseMappingTemplate: "$util.toJson($ctx.result)" 125 | 126 | AppSyncDeleteMutationResolver: 127 | Type: AWS::AppSync::Resolver 128 | DependsOn: AppSyncSchema 129 | Properties: 130 | ApiId: !GetAtt AppSyncApi.ApiId 131 | TypeName: Mutation 132 | FieldName: delete 133 | DataSourceName: !GetAtt AppSyncTableDataSource.Name 134 | RequestMappingTemplate: !Sub | 135 | { 136 | "version": "2017-02-28", 137 | "operation": "DeleteItem", 138 | "key": { 139 | "${TableName}Id": $util.dynamodb.toDynamoDBJson($ctx.args.${TableName}Id) 140 | } 141 | } 142 | ResponseMappingTemplate: "$util.toJson($ctx.result)" 143 | 144 | DynamoDBRole: 145 | Type: AWS::IAM::Role 146 | Properties: 147 | RoleName: !Sub ${TableName}-appsync-dynamodb-role 148 | ManagedPolicyArns: 149 | - arn:aws:iam::aws:policy/AmazonDynamoDBFullAccess 150 | AssumeRolePolicyDocument: 151 | Version: 2012-10-17 152 | Statement: 153 | - Effect: Allow 154 | Action: 155 | - sts:AssumeRole 156 | Principal: 157 | Service: 158 | - appsync.amazonaws.com 159 | 160 | DynamoDBTable: 161 | Type: AWS::DynamoDB::Table 162 | Properties: 163 | TableName: !Ref TableName 164 | AttributeDefinitions: 165 | - 166 | AttributeName: !Sub ${TableName}Id 167 | AttributeType: S 168 | KeySchema: 169 | - 170 | AttributeName: !Sub ${TableName}Id 171 | KeyType: HASH 172 | ProvisionedThroughput: 173 | ReadCapacityUnits: 5 174 | WriteCapacityUnits: 5 175 | StreamSpecification: 176 | StreamViewType: NEW_IMAGE 177 | 178 | Outputs: 179 | DynamoDBTable: 180 | Description: The name of the DynamoDB Table 181 | Value: !Ref DynamoDBTable 182 | GraphQLApiEndpoint: 183 | Description: The URL to the GraphQL Endpoint 184 | Value: !GetAtt AppSyncApi.GraphQLUrl 185 | GraphQLApiId: 186 | Description: The API ID of the GraphQL API 187 | Value: !GetAtt AppSyncApi.ApiId 188 | APIKey: 189 | Description: API Key 190 | Value: !GetAtt AppSyncAPIKey.ApiKey --------------------------------------------------------------------------------