What We’re Building
Previously, I had used AWS SAM to build a CRUD app for notes. Recently, I wanted to learn more about the AWS Cloud Development Kit (CDK), GraphQL, and AWS AppSync, so I decided to refactor my original project.
Disclaimer that at the time of writing the CDK v2 is in developer preview. This blog post will use CDK v1.
If you want to skip ahead, take a look the finished project here.
Setup
If this is your first time using the CDK, bootstrap your AWS account. The Prerequisites
section in this guide has the steps.
Start by installing the CDK npm package and running the cdk init command. This is helpful because it gives you a directory structure along with a very minimal stack to get started.
On top of the initial scaffolding in /lib/note-service-stack.ts
let’s add some extra imports.
import {
CfnOutput,
Construct,
RemovalPolicy,
Stack,
StackProps,
} from "@aws-cdk/core"
import { FieldLogLevel, GraphqlApi, Schema } from "@aws-cdk/aws-appsync"
import { AttributeType, BillingMode, Table } from "@aws-cdk/aws-dynamodb"
import { Code, Function, Runtime } from "@aws-cdk/aws-lambda"
The notes will be persisted in DynamoDB, so let’s also add the following CDK code to create a notes-table.
const notesTable = new Table(this, "NotesTable", {
billingMode: BillingMode.PAY_PER_REQUEST,
partitionKey: { name: "id", type: AttributeType.STRING },
removalPolicy: RemovalPolicy.DESTROY,
tableName: "notes-table",
})
Create the API
The next step is to set up our api like this.
const api = new GraphqlApi(this, "NotesApi", {
name: "notes-api",
logConfig: {
fieldLogLevel: FieldLogLevel.ERROR,
},
schema: Schema.fromAsset("src/graphql/schema.graphql"),
})
The schema property points to a file in the repository. You can also provide this inline if your prefer. The schema needs to be well formed in order to have a successful deploy of the stack.
Between the API and DataStore
The basic flow for GraphQL is:
schema => resolver => data source
The schema is your API contract. It says here’s my query and mutation definitions in this API and he’s what I expect to receive back from this specific query or mutation. Each field in that schema definition can be connected to a resolver that connects to the datasource.
For example our Query to get a note by id looks like this
getNote(id: ID!): Note
It takes an id
parameter and expectes a Note
to be returned. A Note
is defined in the schema to look like this
type Note {
id: ID!
content: String!
author: String!
createdAt: String!
updatedAt: String
}
Traditionally on AWS one would use Velocity Template Language (VTL) to resolve GraphQL schema fields to a datasource; however, last year AWS released the concept of a direct Lambda resolver. There’s definitely reasons to continue using VTL over direct Lambda resolvers that I won’t get into here, but I personally was pretty excited about this development since it meant that I could directly resolve to a Lambda used as a data source without needing to write any VTL.
Create Resolver and Data Source
For demonstration purposes let’s focus on the getNote
Query. This is a query that a client will make to this API that will send a note id and receive the note back.
Keep in mind that you’ll also need to do similar things for all other queries/mutations in your API (in this case it’s listNotes
query, createNote
mutation, deleteNote
mutation, updateNote
mutation).
Let’s start by adding some CDK code to create the Lambda function
const getLambda = new Function(this, "GetLambda", {
functionName: "get-lambda",
runtime: Runtime.NODEJS_14_X,
handler: "get.handler",
code: Code.fromAsset("src/get"),
memorySize: 3008,
})
The code
property points to the source code for this function in src/get
. This will contain the code necessary to retrieve our note from DynamoDB.
We’ll then want to add this Lambda as a data source to the api we previously created.
const getDs = api.addLambdaDataSource("getDatasource", getLambda)
Next let’s create a resolver to resolve the getNote
Query field in our schema to our Lambda data source.
getDs.createResolver({
typeName: "Query",
fieldName: "getNote",
})
The final step is to ensure our Lamba has proper permissions to perform operations on DynamoDB, so let’s send it the table name as an environment variable and grant it read permissions.
getLambda.addEnvironment("TABLE_NAME", notesTable.tableName)
notesTable.grantReadData(getLambda)
Finished Project
I personally have really enjoyed using GraphQL and the AWS CDK in my projects and hope you do too. Check out the full example here. Contributions are welcome.