Building a serverless ledger on AWS with TypeScript and CDK v2 (Part 1)

5 min read Original article ↗

Solution overview and project setup

McKinsey Digital

by

In this three-part series, I want to show you one way to build and deploy a simple financial application that would allow authenticated clients to create accounts, and to transfer funds between accounts.

Below is an overview of what I’ll cover throughout the series:

  1. Solution overview and project setup (this part)
  2. Handling business logic with Lambda
  3. Security and monitoring

Solution overview

We will work with the AWS CDK (v2) in TypeScript to create and deploy various infrastructure and business logic handling components. The solution is 100% serverless, leveraging components such as DynamoDB, API Gateway, Lambda, and others. It should, for the most part, fall within the Free Tier.

I will expose the following REST endpoints and protect them with API keys configured with throttling and quotas, all managed by the API Gateway:

  1. POST /accounts; Creates a new account
  2. POST /transfers; Performs a transfer between accounts
  3. GET /accounts/:id/transactions; Lists transactions for a given account

For certain operations, such as funding accounts, we’ll be using the AWS console directly.

Project setup

This guide assumes you’ve got an existing AWS account and at least one IAM user with programmatic access and admin permissions:

Press enter or click to view image in full size

Adding IAM user with programmatic access

Press enter or click to view image in full size

Attaching AdministratorAccess policy to IAM user’s permissions

You can go through this guide if you’re unsure how to do this. Upon successful user creation, you should be able to view and copy your access key id and secret access key.

It is good practice to use profiles when using multiple AWS configurations on your local machine. Grab your credentials and create a new profile by adding the following block to ~/.aws/credentials (create this file if it does not exist):

[ledger-dev]
aws_access_key_id=YOUR_ACCESS_KEY_ID
aws_secret_access_key=YOUR_SECRET_ACCESS_KEY

With this out of the way, let’s initialise the ledger project. I prefer explicitly specifying all dependencies, including the TypeScript compiler and the CDK toolkit, as this lets you pin components to specific versions and ensure engineers from other teams (as well as the CI/CD environment) use exactly those versions. This eliminates a possible source of change, helping to make builds and deployments more consistent and repeatable.

> mkdir ledger; cd ledger; git init;
ledger> echo "node_modules" > .gitignore
ledger> npm i -D aws-cdk

To verify the installation:

ledger> npx cdk --version
2.8.0 (build 8a5eb49)

To deploy your resources to a specific region using the CDK toolkit, you will need to perform a one-time setup called Bootstrapping. Throughout the series I’ll be deploying to 🇬🇧 eu-west-2 as that’s where I am based but you may, of course, choose a different region.

ledger> npx cdk bootstrap aws://YOUR-ACCOUNT-NUMBER/YOUR-REGION --profile ledger-dev

Once this has completed, you can verify that the stack has been created via the CloudFormation console (👀 don’t forget to select the correct region):

Press enter or click to view image in full size

Adding resources

With the CDK toolkit being ready, let’s add the first few resources, namely a DynamoDB Table and a REST API Gateway.

To use the CDK v2, install:

ledger> npm i aws-cdk-lib constructs

You will also need to add the dependencies required to compile TypeScript code.

ledger> npm i -D typescript ts-node tsconfig-paths source-map-support

I like to use the strictest tsc compiler configuration available, and I also set up some basic path mappings to help shorten and simplify import statements throughout the application code. For reference, below is the tsconfig.json I’ll be using.

Pro tip: You can also use cdk init sample-app --language typescript to fast-track the bootstrap process.

To keep the application nice and tidy, I will organise resources into (largely pre-configured) Constructs and place them under the lib/constructs directory. To learn more about Constructs please consult the official docs.

First, let’s create the DynamoDB Table Construct with minimal configuration in lib/constructs/table.ts:

lib/constructs/table.ts

And then the API Gateway Construct in lib/constructs/apiGateway.ts:

lib/constructs/apiGateway.ts

Now, let’s add these into a Stack. A Stack is the basic unit of deployment in the AWS CDK. To learn more about Stacks, please consult the official docs.

Pro tip: It is also possible to create multiple stacks, for example one with stateful resources, which don’t change often, and one with resources that change more frequently such as Lambda functions.

In lib/ledgerStack.ts:

lib/ledgerStack.ts

First deployment

Finally, it’s time to deploy. You’ll need to create a script that the CDK toolkit will use to synthesise CloudFormation templates. The idea is to create an App and then register stacks configured for a specific environment within it. I will call this specific LedgerStack configuration "dev" (this is also what I’ll see in the CloudFormation console once it’s deployed). Take note of how I’m passing the apiStageName prop down to LedgerStack which will, in turn, pass it down to ApiGateway.

In bin/ledger.ts:

bin/ledger.ts

Lastly, you’ll need a cdk.json file in the root of the project to tell the CDK how to execute the script above.

cdk.json

Let’s try to synthesise the dev stack:

ledger> npx cdk synth dev

You should see some outputs in the console as well as files being created in cdk.out. Make sure to add this to your .gitignore:

ledger> echo "cdk.out" >> .gitignore

To deploy:

ledger> npx cdk deploy dev --profile ledger-dev

You will likely be prompted to confirm the deployment as there are a number of security broadening changes about to take place. A successful deployment should look something like this:

Press enter or click to view image in full size

Successful first deployment

You can verify that the dev stack has been successfully deployed via the CloudFormation console:

Press enter or click to view image in full size

You should also be able to see the DynamoDB table and the (REST) API Gateway in their respective consoles:

Press enter or click to view image in full size

DynamoDB Table

Press enter or click to view image in full size

REST API

Although the execution endpoint is now available, trying to invoke it will likely result in Internal server error as there are no integrations configured yet.

ledger> curl https://xxxxxxxxxx.execute-api.eu-west-2.amazonaws.com/dev/
{"message": "Internal server error"}

Well done, that’s Part 1 done! 🎉

In Part 2, you will learn how to implement business logic with Lambda functions and bind them to specific API Gateway endpoints for execution.

References: Complete source code for the article