This is a "walking skeleton" AWS Lambda app, using TypeScript, CDK, Jest, and Github Actions. It is fully deployable, includes tests, and has a Github Actions workflow that will perform integration tests on an ephemeral deployment in AWS.
In other words you can use this repo as a basis for making your own TypeScript Lambda-based applications.
This is an updated version of a demo app I originally put together in 2020 for Cloud Coffee Break, a YouTube series I recorded.
This version contains the following changes since the 2020 version:
- Uses TypeScript instead of Javascript
- Uses CDK instead of SAM
- Uses Github Actions instead of AWS CodeBuild for automation. Github Actions-to-AWS security uses OIDC, not long-lived access tokens.
- Uses a Lambda Function URL instead of API Gateway
This example is part of a collection of CDK examples - others are as follows:
- CDK bare-bones app for TypeScript - Base project for any TypeScript app using CDK for deployment to AWS. Try this first if you are getting started with CDK.
- Coffee Store Web Basic - Website hosting on AWS with CloudFront and S3
- Coffee Store Web Full - A further extension of Coffee Store Web Basic that is a real working demo of a production-ready website project, including TLS certificates, DNS hosting, Github Actions Workflows, multiple CDK environments (prod vs test vs dev)
This example deploys a CDK App that deploys a Lambda Function, together with a Lambda Function URL to make it accessible over HTTP.
To build the Lambda function, this example uses the NodejsFunction
CDK Construct which performs build actions as part of the deploy process. In this configuration the Construct:
- Runs TSC in "noEmit" mode to perform type-checking
- Runs Esbuild to create an optimized artifact for the Lambda function
Using NodejsFunction
makes CDK a build tool and not just a deployment tool. In the past I've been hesitant to use this feature since it didn't feel like it worked well for me.
As of August 2022, however, I feel like NodejsFunction
is probably "good enough" for many small to medium size TS Lambda projects. There's still one small bug for NodejsFunction
in how I have TSC configured (see the comment in tsconfig.json), but there's a reasonable workaround for that.
If you'd like more control over your build process then swap NodejsFunction
for the standard CDK Function
construct, and add a build phase to your project. To see an example of this, including a wrapper script for ESBuild, see the earlier version of this project.
Please see the prerequisites of the cdk-bare-bones project - all of the prerequisites in that project also apply to this one.
After cloning this project to your local machine, run the following to perform local checks on the code:
$ npm install && npm run type-check-and-unit-test
If successful, the end result will look something like this:
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 0.253 s
Ran all test suites.
The unit tests run entirely locally, and execute the code directly, i.e. they do not use local runtime simulators. For the reasoning behind this choice see https://blog.symphonia.io/posts/2020-08-19_serverless_testing .
Once you've run npm install once in the directory you won't need to again
To deploy the application to AWS run the following:
$ npm run deploy
If successful, the end result will look something like this:
✅ CoffeeStore (coffee-store-v2)
✨ Deployment time: 64.38s
Outputs:
CoffeeStore.ApiUrl = https://t7bz3kq6zhpqiotdgp3tpvv7ie0vkpcw.lambda-url.us-east-1.on.aws/
Stack ARN:
arn:aws:cloudformation:us-east-1:397589511426:stack/coffee-store-v2/7ef9afc0-1a72-11ed-91e9-0e87df064301
✨ Total time: 68.29s
That CoffeeStore.ApiUrl
value is a public URL - access it with your browser and you should see a "Hello World" message.
For other commands, including how to teardown, see the Usage section of the bare-bones project README.
- Install dependencies with
npm install
, or your IDE's Node support. - Run unit tests using the configuration at __tests__/jest-unit.config.js
This project includes an integration test which calls the deployed app in AWS via https and validates the response.
If you want to run the integration tests against a stack that has already been deployed you should specify the stack's name with
the STACK_NAME
environment variable, e.g.:
$ STACK_NAME=coffee-store-v2 npm run integration-test
If you are running tests via the IDE:
- Use the jest configuration at at __tests__/jest-integration.config.js
- make sure to specify the
STACK_NAME
env var as part of your test runner configuration if you want to use a pre-deployed stack.
Alternatively the integration test can run against an ephemeral stack - i.e. a new stack will be deployed as part of
test setup, and then torn down as part of test cleanup. To use this method don't
specify a STACK_NAME
value in the environment.
E.g. if you run npm run integration-test
with no STACK_NAME
you will see something like the following in the console output
while the integration test is being run:
console.log
Starting cloudformation deployment of stack coffee-store-it-20220425-160643
at integration/api-integration.test.ts:15:17
and a little later you will see:
console.log
Calling cloudformation to delete stack coffee-store-it-20220425-160643
The example above shows the test creating a stack named stack coffee-store-it-20220425-160643
. If you want an
alternative to the start of the name being coffee-store-it
you can specify
the STACK_NAME_PREFIX
environment variable when running the integration test.
The included Github Actions workflow at .github/workflows/buildAndTest.yml will run all tests. Since these tests include the integration tests, the workflow will (indirectly) deploy an ephemeral version of the application to AWS, and therefore the Github Actions workflow needs permission to access your AWS account.
For instructions on how to setup these permissions, see github-actions-prereqs/README.md.
The included workflow will either run automatically whenever you push a change to the main
branch, or when you run
the workflow manually through the web console.
You might choose to update the workflow to also deploy a non-ephemeral version of the app. To do this add running
npm run deploy
to your workflow - in which case you may also want to specify the STACK_NAME
env var.
If you have questions related to this example please add a Github issue, or drop me a line at mike@symphonia.io . I'm also on Twitter at @mikebroberts .
- Update project dependency versions
- Move cdk.json to src/cdk directory. This is for a couple of reasons:
- One fewer file in project root, which I think is A Good Thing
- Makes it easier to have repos with multiple, separate, CDK apps
- Update lambdaFunction definition for new relative location of app source code
- This is necessary because of the previous change - CDK is going to be running in a different directory now
- Move "output" and "requireApproval" CDK settings from package.json to cdk.json
- I hadn't read the docs enough to know they could be in cdk.json. Oops. This way is cleaner
- Use CDK
build
step to run TSC instead ofpreCompilation
in NodejsFunction- Getting NodejsFunction to run tsc just for type-checking is problematic, but I had it working in previous version. I hadn't realized before that the "build" CDK property applied to all synth activities, not just "cdk watch", and so putting project-wide type checking in the "build" property, combined with still using NodejsFunction for running esbuild, is a cleaner solution. This allows cleaning up the previous workaround in tsconfig.json
- Update Node to Node 16 (tooling + runtime)
- Switch to CDK for building, by using NodejsFunction
- Standardize project setup vs https://github.com/symphoniacloud/cdk-bare-bones
- Update this README, referring to bare bones project to avoid duplication
- Remove username based stack naming to simplify things a little
- Uses TypeScript instead of Javascript, plus some opinionated build + package scripting
- Uses CDK instead of SAM
- Uses Github Actions instead of AWS CodeBuild for automation. Github Actions-to-AWS security uses OIDC, not long-lived access tokens.
- Uses a Lambda Function URL instead of API Gateway