Step 4: Using an API: CDK example - Amazon AppSync
Services or capabilities described in Amazon Web Services documentation might vary by Region. To see the differences applicable to the China Regions, see Getting Started with Amazon Web Services in China (PDF).

Step 4: Using an API: CDK example

Tip

Before you use the CDK, we recommend reviewing the CDK's official documentation along with Amazon AppSync's CDK reference.

We also recommend ensuring that your Amazon CLI and NPM installations are working on your system.

In this section, we're going to create a simple CDK app that can add and fetch items from a DynamoDB table. This is meant to be a quickstart example using some of the code from the Designing your schema, Attaching a data source, and Configuring resolvers (JavaScript) sections.

Setting up a CDK project

Warning

These steps may not be completely accurate depending on your environment. We're assuming your system has the necessary utilities installed, a way to interface with Amazon services, and proper configurations in place.

The first step is installing the Amazon CDK. In your CLI, you can enter the following command:

npm install -g aws-cdk

Next, you need to create a project directory, then navigate to it. An example set of commands to create and navigate to a directory is:

mkdir example-cdk-app cd example-cdk-app

Next, you need to create an app. Our service primarily uses TypeScript. In your project directory, enter the following command:

cdk init app --language typescript

When you do this, a CDK app along with its initialization files will be installed:

Your project structure may look like this:

You'll notice we have several important directories:

  • bin: The initial bin file will create the app. We won't touch this in this guide.

  • lib: The lib directory contains your stack files. You can think of stack files as individual units of execution. Constructs will be inside our stack files. Basically, these are resources for a service that will be spun up in Amazon CloudFormation when the app is deployed. This is where most of our coding will happen.

  • node_modules: This directory is created by NPM and contains all package dependencies you installed using the npm command.

Our initial stack file may contain something like this:

import * as cdk from 'aws-cdk-lib'; import { Construct } from 'constructs'; // import * as sqs from 'aws-cdk-lib/aws-sqs'; export class ExampleCdkAppStack extends cdk.Stack { constructor(scope: Construct, id: string, props?: cdk.StackProps) { super(scope, id, props); // The code that defines your stack goes here // example resource // const queue = new sqs.Queue(this, 'ExampleCdkAppQueue', { // visibilityTimeout: cdk.Duration.seconds(300) // }); } }

This is the boilerplate code to create a stack in our app. Most of our code in this example will go inside the scope of this class.

To verify that your stack file is in the app, in your app's directory, run the following command in the terminal:

cdk ls

A list of your stacks should appear. If it doesn't, then you may need to run through the steps again or check the official documentation for help.

If you want to build your code changes before deploying, you can always run the following command in the terminal:

npm run build

And, to see the changes before deploying:

cdk diff

Before we add our code to the stack file, we're going to perform a bootstrap. Bootstrapping allows us to provision resources for the CDK before the app deploys. More information about this process can be found here. To create a bootstrap, the command is:

cdk bootstrap aws://ACCOUNT-NUMBER/REGION
Tip

This step requires several IAM permissions in your account. Your bootstrap will be denied if you don't have them. If this happens, you may have to delete incomplete resources caused by the bootstrap such as the S3 bucket it generates.

Bootstrap will spin up several resources. The final message will look like this:

This is done once per account per Region, so you won't have to do this often. The main resources of the bootstrap are the Amazon CloudFormation stack and the Amazon S3 bucket.

The Amazon S3 bucket is used to store files and IAM roles that grant permissions needed to perform deployments. The required resources are defined in an Amazon CloudFormation stack, called the bootstrap stack, which is usually named CDKToolkit. Like any Amazon CloudFormation stack, it appears in the Amazon CloudFormation console once it has been deployed:

The same can be said for the bucket:

To import the services we need in our stack file, we can use the following command:

npm install aws-cdk-lib # V2 command
Tip

If you're having trouble with V2, you could install the individual libraries using V1 commands:

npm install @aws-cdk/aws-appsync @aws-cdk/aws-dynamodb

We don't recommend this because V1 has been deprecated.

Implementing a CDK project - Schema

We can now start implementing our code. First, we must create our schema. You can simply create a .graphql file in your app:

mkdir schema touch schema.graphql

In our example, we included a top-level directory called schema containing our schema.graphql:

Inside our schema, let's include a simple example:

input CreatePostInput { title: String content: String } type Post { id: ID! title: String content: String } type Mutation { createPost(input: CreatePostInput!): Post } type Query { getPost: [Post] }

Back in our stack file, we need to make sure the following import directives are defined:

import * as cdk from 'aws-cdk-lib'; import * as appsync from 'aws-cdk-lib/aws-appsync'; import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; import { Construct } from 'constructs';

Inside the class, we'll add code to make our GraphQL API and connect it to our schema.graphql file:

export class ExampleCdkAppStack extends cdk.Stack { constructor(scope: Construct, id: string, props?: cdk.StackProps) { super(scope, id, props); // makes a GraphQL API const api = new appsync.GraphqlApi(this, 'post-apis', { name: 'api-to-process-posts', schema: appsync.SchemaFile.fromAsset('schema/schema.graphql'), }); } }

We'll also add some code to print out the GraphQL URL, API key, and Region:

export class ExampleCdkAppStack extends cdk.Stack { constructor(scope: Construct, id: string, props?: cdk.StackProps) { super(scope, id, props); // Makes a GraphQL API construct const api = new appsync.GraphqlApi(this, 'post-apis', { name: 'api-to-process-posts', schema: appsync.SchemaFile.fromAsset('schema/schema.graphql'), }); // Prints out URL new cdk.CfnOutput(this, "GraphQLAPIURL", { value: api.graphqlUrl }); // Prints out the AppSync GraphQL API key to the terminal new cdk.CfnOutput(this, "GraphQLAPIKey", { value: api.apiKey || '' }); // Prints out the stack region to the terminal new cdk.CfnOutput(this, "Stack Region", { value: this.region }); } }

At this point, we'll use deploy our app again:

cdk deploy

This is the result:

It appears our example was successful, but let's check the Amazon AppSync console just to confirm:

It appears our API was created. Now, we'll check the schema attached to the API:

This appears to match up with our schema code, so it was successful. Another way to confirm this from a metadata viewpoint is to look at the Amazon CloudFormation stack:

When we deploy our CDK app, it goes through Amazon CloudFormation to spin up resources like the bootstrap. Each stack within our app maps 1:1 with an Amazon CloudFormation stack. If you go back to the stack code, the stack name was grabbed from the class name ExampleCdkAppStack. You can see the resources it created, which also match our naming conventions in our GraphQL API construct:

Implementing a CDK project - Data source

Next, we need to add our data source. Our example will use a DynamoDB table. Inside the stack class, we'll add some code to create a new table:

export class ExampleCdkAppStack extends cdk.Stack { constructor(scope: Construct, id: string, props?: cdk.StackProps) { super(scope, id, props); // Makes a GraphQL API construct const api = new appsync.GraphqlApi(this, 'post-apis', { name: 'api-to-process-posts', schema: appsync.SchemaFile.fromAsset('schema/schema.graphql'), }); //creates a DDB table const add_ddb_table = new dynamodb.Table(this, 'posts-table', { partitionKey: { name: 'id', type: dynamodb.AttributeType.STRING, }, }); // Prints out URL new cdk.CfnOutput(this, "GraphQLAPIURL", { value: api.graphqlUrl }); // Prints out the AppSync GraphQL API key to the terminal new cdk.CfnOutput(this, "GraphQLAPIKey", { value: api.apiKey || '' }); // Prints out the stack region to the terminal new cdk.CfnOutput(this, "Stack Region", { value: this.region }); } }

At this point, let's deploy again:

cdk deploy

We should check the DynamoDB console for our new table:

Our stack name is correct, and the table name matches our code. If we check our Amazon CloudFormation stack again, we'll now see the new table:

Implementing a CDK project - Resolver

This example will use two resolvers: one to query the table and one to add to it. Since we're using pipeline resolvers, we'll need to declare two pipeline resolvers with one function in each. In the query, we'll add the following code:

export class ExampleCdkAppStack extends cdk.Stack { constructor(scope: Construct, id: string, props?: cdk.StackProps) { super(scope, id, props); // Makes a GraphQL API construct const api = new appsync.GraphqlApi(this, 'post-apis', { name: 'api-to-process-posts', schema: appsync.SchemaFile.fromAsset('schema/schema.graphql'), }); //creates a DDB table const add_ddb_table = new dynamodb.Table(this, 'posts-table', { partitionKey: { name: 'id', type: dynamodb.AttributeType.STRING, }, }); // Creates a function for query const add_func = new appsync.AppsyncFunction(this, 'func-get-post', { name: 'get_posts_func_1', api, dataSource: api.addDynamoDbDataSource('table-for-posts', add_ddb_table), code: appsync.Code.fromInline(` export function request(ctx) { return { operation: 'Scan' }; } export function response(ctx) { return ctx.result.items; } `), runtime: appsync.FunctionRuntime.JS_1_0_0, }); // Creates a function for mutation const add_func_2 = new appsync.AppsyncFunction(this, 'func-add-post', { name: 'add_posts_func_1', api, dataSource: api.addDynamoDbDataSource('table-for-posts-2', add_ddb_table), code: appsync.Code.fromInline(` export function request(ctx) { return { operation: 'PutItem', key: util.dynamodb.toMapValues({id: util.autoId()}), attributeValues: util.dynamodb.toMapValues(ctx.args.input), }; } export function response(ctx) { return ctx.result; } `), runtime: appsync.FunctionRuntime.JS_1_0_0, }); // Adds a pipeline resolver with the get function new appsync.Resolver(this, 'pipeline-resolver-get-posts', { api, typeName: 'Query', fieldName: 'getPost', code: appsync.Code.fromInline(` export function request(ctx) { return {}; } export function response(ctx) { return ctx.prev.result; } `), runtime: appsync.FunctionRuntime.JS_1_0_0, pipelineConfig: [add_func], }); // Adds a pipeline resolver with the create function new appsync.Resolver(this, 'pipeline-resolver-create-posts', { api, typeName: 'Mutation', fieldName: 'createPost', code: appsync.Code.fromInline(` export function request(ctx) { return {}; } export function response(ctx) { return ctx.prev.result; } `), runtime: appsync.FunctionRuntime.JS_1_0_0, pipelineConfig: [add_func_2], }); // Prints out URL new cdk.CfnOutput(this, "GraphQLAPIURL", { value: api.graphqlUrl }); // Prints out the AppSync GraphQL API key to the terminal new cdk.CfnOutput(this, "GraphQLAPIKey", { value: api.apiKey || '' }); // Prints out the stack region to the terminal new cdk.CfnOutput(this, "Stack Region", { value: this.region }); } }

In this snippet, we added a pipeline resolver called pipeline-resolver-create-posts with a function called func-add-post attached to it. This is the code that will add Posts to the table. The other pipeline resolver was called pipeline-resolver-get-posts with a function called func-get-post that retrieves Posts added to the table.

We'll deploy this to add it to the Amazon AppSync service:

cdk deploy

Let's check the Amazon AppSync console to see if they were attached to our GraphQL API:

It appears to be correct. In the code, both of these resolvers were attached to the GraphQL API we made (denoted by the api props value present in both the resolvers and functions). In the GraphQL API, the fields we attached our resolvers to were also specified in the props (defined by the typename and fieldname props in each resolver).

Let's see if the content of the resolvers is correct starting with the pipeline-resolver-get-posts:

The before and after handlers match our code props value. We can also see that a function called add_posts_func_1, which matches the name of the function we attached in the resolver.

Let's look at the code content of that function:

This matches up with the code props of the add_posts_func_1 function. Our query was successfully uploaded, so let's check on the query:

These also match the code. If we look at get_posts_func_1:

Everything appears to be in place. To confirm this from a metadata perspective, we can check our stack in Amazon CloudFormation again:

Now, we need to test this code by performing some requests.

Implementing a CDK project - Requests

To test our app in the Amazon AppSync console, we made one query and one mutation:

MyMutation contains a createPost operation with the arguments 1970-01-01T12:30:00.000Z and first post. It returns the date and title that we passed in as well as the automatically generated id value. Running the mutation yields the result:

{ "data": { "createPost": { "date": "1970-01-01T12:30:00.000Z", "id": "4dc1c2dd-0aa3-4055-9eca-7c140062ada2", "title": "first post" } } }

If we check the DynamoDB table quickly, we can see our entry in the table when we scan it:

Back in the Amazon AppSync console, if we run the query to retrieve this Post, we get the following result:

{ "data": { "getPost": [ { "id": "9f62c4dd-49d5-48d5-b835-143284c72fe0", "date": "1970-01-01T12:30:00.000Z", "title": "first post" } ] } }