Skip to content

Commit 4fb2710

Browse files
simalexanrix0rrr
authored andcommitted
feat: add example for AppSync GraphQL w/ DynamoDB CRUD resolvers (#44)
Add a AWS CDK example for an AppSync GraphQL API with an API Key, and CRUD Resolvers with DynamoDB. There are different Resolvers doing two Queries to Get One, All and two Mutations doing Save Item and Delete Item. Should be useful for developers starting with AWS CDK on how to run a GraphQL API with Resolvers and DynamoDB. .
1 parent 6001742 commit 4fb2710

File tree

8 files changed

+214
-2
lines changed

8 files changed

+214
-2
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ $ cdk destroy
2222
|---------|-------------|
2323
| [api-cors-lambda-crud-dynamodb](https://github.com/aws-samples/aws-cdk-examples/tree/master/typescript/api-cors-lambda-crud-dynamodb/) | Creating a single API with CORS, and five Lambdas doing CRUD operations over a single DynamoDB |
2424
| [application-load-balancer](https://github.com/aws-samples/aws-cdk-examples/tree/master/typescript/application-load-balancer/) | Using an AutoScalingGroup with an Application Load Balancer |
25+
| [appsync-graphql-dynamodb](https://github.com/aws-samples/aws-cdk-examples/tree/master/typescript/appsync-graphql-dynamodb/) | Creating a single GraphQL API with an API Key, and four Resolvers doing CRUD operations over a single DynamoDB |
2526
| [classic-load-balancer](https://github.com/aws-samples/aws-cdk-examples/tree/master/typescript/classic-load-balancer/) | Using an AutoScalingGroup with a Classic Load Balancer |
2627
| [custom-resource](https://github.com/aws-samples/aws-cdk-examples/tree/master/typescript/custom-resource/) | Shows adding a Custom Resource to your CDK app |
2728
| [elasticbeanstalk](https://github.com/aws-samples/aws-cdk-examples/tree/master/typescript/elasticbeanstalk/) | Elastic Beanstalk example using L1 with a Blue/Green pipeline (community contributed) |
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# AppSync GraphQL API with four resolvers CRUD with DynamoDB
2+
3+
This an example of an AppSync GraphQL API, pointing to four resolvers doing CRUD operations with a single DynamoDB table.
4+
5+
## The Component Structure
6+
7+
This Stack contains:
8+
9+
- a GraphQL API with an API Key (Use with caution, each key is only valid for 7 days.)
10+
- a GraphQL Schema with Queries to get one and all items and two mutations to save and delete an item
11+
- a DynamoDB table `items` that stores the data with a Pay Per Request Billing Mode
12+
- an IAM Role that allows AppSync to invoke your DynamoDB table.
13+
- a DataSource, connecting your API to the DynamoDB table with the previously specified role.
14+
- a Resolver for a Query `getOne` to get one item from the DynamoDB table.
15+
- a Resolver for a Query `all` to get all items from the DynamoDB table.
16+
- a Resolver for a Mutation `save` to put an item into the DynamoDB table (the id is autogenerated, you need only name).
17+
- a Resolver for a Mutation `delete` to delete one item from the DynamoDB table.
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"app": "node index"
3+
}
Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
import cdk = require('@aws-cdk/cdk');
2+
import { CfnGraphQLApi, CfnApiKey, CfnGraphQLSchema, CfnDataSource, CfnResolver } from '@aws-cdk/aws-appsync';
3+
import { Table, AttributeType, StreamViewType, BillingMode } from '@aws-cdk/aws-dynamodb';
4+
import { Role, ServicePrincipal } from '@aws-cdk/aws-iam';
5+
6+
7+
export class AppSyncCdkStack extends cdk.Stack {
8+
9+
constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) {
10+
super(scope, id, props);
11+
12+
const tableName = 'items'
13+
14+
const itemsGraphQLApi = new CfnGraphQLApi(this, 'ItemsApi', {
15+
name: 'items-api',
16+
authenticationType: 'API_KEY'
17+
});
18+
19+
new CfnApiKey(this, 'ItemsApiKey', {
20+
apiId: itemsGraphQLApi.graphQlApiApiId
21+
});
22+
23+
const apiSchema = new CfnGraphQLSchema(this, 'ItemsSchema', {
24+
apiId: itemsGraphQLApi.graphQlApiApiId,
25+
definition: `type ${tableName} {
26+
${tableName}Id: ID!
27+
name: String
28+
}
29+
type Paginated${tableName} {
30+
items: [${tableName}!]!
31+
nextToken: String
32+
}
33+
type Query {
34+
all(limit: Int, nextToken: String): Paginated${tableName}!
35+
getOne(${tableName}Id: ID!): ${tableName}
36+
}
37+
type Mutation {
38+
save(name: String!): ${tableName}
39+
delete(${tableName}Id: ID!): ${tableName}
40+
}
41+
type Schema {
42+
query: Query
43+
mutation: Mutation
44+
}`
45+
});
46+
47+
const itemsTable = new Table(this, 'ItemsTable', {
48+
tableName: tableName,
49+
partitionKey: {
50+
name: `${tableName}Id`,
51+
type: AttributeType.String
52+
},
53+
billingMode: BillingMode.PayPerRequest,
54+
streamSpecification: StreamViewType.NewImage
55+
});
56+
57+
const itemsTableRole = new Role(this, 'ItemsDynamoDBRole', {
58+
assumedBy: new ServicePrincipal('appsync.amazonaws.com')
59+
});
60+
61+
itemsTableRole.attachManagedPolicy('arn:aws:iam::aws:policy/AmazonDynamoDBFullAccess');
62+
63+
const dataSource = new CfnDataSource(this, 'ItemsDataSource', {
64+
apiId: itemsGraphQLApi.graphQlApiApiId,
65+
name: 'ItemsDynamoDataSource',
66+
type: 'AMAZON_DYNAMODB',
67+
dynamoDbConfig: {
68+
tableName: itemsTable.tableName,
69+
awsRegion: this.region
70+
},
71+
serviceRoleArn: itemsTableRole.roleArn
72+
});
73+
74+
const getOneResolver = new CfnResolver(this, 'GetOneQueryResolver', {
75+
apiId: itemsGraphQLApi.graphQlApiApiId,
76+
typeName: 'Query',
77+
fieldName: 'getOne',
78+
dataSourceName: dataSource.dataSourceName,
79+
requestMappingTemplate: `{
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+
getOneResolver.addDependsOn(apiSchema);
89+
90+
const getAllResolver = new CfnResolver(this, 'GetAllQueryResolver', {
91+
apiId: itemsGraphQLApi.graphQlApiApiId,
92+
typeName: 'Query',
93+
fieldName: 'all',
94+
dataSourceName: dataSource.dataSourceName,
95+
requestMappingTemplate: `{
96+
"version": "2017-02-28",
97+
"operation": "Scan",
98+
"limit": $util.defaultIfNull($ctx.args.limit, 20),
99+
"nextToken": $util.toJson($util.defaultIfNullOrEmpty($ctx.args.nextToken, null))
100+
}`,
101+
responseMappingTemplate: `$util.toJson($ctx.result)`
102+
});
103+
getAllResolver.addDependsOn(apiSchema);
104+
105+
const saveResolver = new CfnResolver(this, 'SaveMutationResolver', {
106+
apiId: itemsGraphQLApi.graphQlApiApiId,
107+
typeName: 'Mutation',
108+
fieldName: 'save',
109+
dataSourceName: dataSource.dataSourceName,
110+
requestMappingTemplate: `{
111+
"version": "2017-02-28",
112+
"operation": "PutItem",
113+
"key": {
114+
"${tableName}Id": { "S": "$util.autoId()" }
115+
},
116+
"attributeValues": {
117+
"name": $util.dynamodb.toDynamoDBJson($ctx.args.name)
118+
}
119+
}`,
120+
responseMappingTemplate: `$util.toJson($ctx.result)`
121+
});
122+
saveResolver.addDependsOn(apiSchema);
123+
124+
const deleteResolver = new CfnResolver(this, 'DeleteMutationResolver', {
125+
apiId: itemsGraphQLApi.graphQlApiApiId,
126+
typeName: 'Mutation',
127+
fieldName: 'delete',
128+
dataSourceName: dataSource.dataSourceName,
129+
requestMappingTemplate: `{
130+
"version": "2017-02-28",
131+
"operation": "DeleteItem",
132+
"key": {
133+
"${tableName}Id": $util.dynamodb.toDynamoDBJson($ctx.args.${tableName}Id)
134+
}
135+
}`,
136+
responseMappingTemplate: `$util.toJson($ctx.result)`
137+
});
138+
deleteResolver.addDependsOn(apiSchema);
139+
140+
}
141+
}
142+
143+
const app = new cdk.App();
144+
new AppSyncCdkStack(app, 'AppSyncGraphQLDynamoDBExample');
145+
app.run();
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
{
2+
"name": "appsync-graphql-dynamodb",
3+
"version": "0.23.0",
4+
"description": "Running a GraphQL API with four resolvers to do CRUD operations on DynamoDB",
5+
"private": true,
6+
"scripts": {
7+
"build": "tsc",
8+
"watch": "tsc -w",
9+
"cdk": "cdk"
10+
},
11+
"author": {
12+
"name": "Aleksandar Simovic <[email protected]>",
13+
"url": "https://serverless.pub"
14+
},
15+
"license": "MIT",
16+
"devDependencies": {
17+
"@types/node": "^8.10.38",
18+
"typescript": "^3.2.4"
19+
},
20+
"dependencies": {
21+
"@aws-cdk/aws-appsync": "*",
22+
"@aws-cdk/aws-dynamodb": "*",
23+
"@aws-cdk/aws-iam": "*",
24+
"@aws-cdk/cdk": "*"
25+
}
26+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
{
2+
"compilerOptions": {
3+
"target":"ES2018",
4+
"module": "commonjs",
5+
"lib": ["es2016", "es2017.object", "es2017.string"],
6+
"strict": true,
7+
"noImplicitAny": true,
8+
"strictNullChecks": true,
9+
"noImplicitThis": true,
10+
"alwaysStrict": true,
11+
"noUnusedLocals": true,
12+
"noUnusedParameters": true,
13+
"noImplicitReturns": true,
14+
"noFallthroughCasesInSwitch": false,
15+
"inlineSourceMap": true,
16+
"inlineSources": true,
17+
"experimentalDecorators": true,
18+
"strictPropertyInitialization":false
19+
}
20+
}
21+

typescript/ecs/cluster/index.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,4 +33,3 @@ const app = new cdk.App();
3333
new ECSCluster(app, 'MyFirstEcsCluster');
3434

3535
app.run();
36-

typescript/ecs/ecs-service-with-task-networking/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,4 +47,4 @@ new ecs.Ec2Service(stack, 'awsvpc-ecs-demo-service', {
4747
securityGroup,
4848
});
4949

50-
app.run();
50+
app.run();

0 commit comments

Comments
 (0)