Deploying Typescript Lambdas to AWS with the CDK
It’s never been easier to write typescript lambdas thanks to the @aws-cdk/aws-lambda-nodejs package, which uses esbuild under the hood to automatically bundle your typescript into javascript! 🛠
To demonstrate this, I’m going to use the AWS CDK to deploy a simple GraphQL lambda written in typescript to AWS, with very minimal config! All you have to do is pass the path to the handler of your lambda and the NodejsFunction will do the rest!
Table of Contents:
- Project Setup
- Building the GraphQL lambda
- Building AWS resources with the CDK
- Deploying to AWS with the CDK
- Bonus! Running the stack locally
- Wrap up
Project Setup
This tutorial was written using node v14.14. You can use the Node Version Manager to make sure you’re using the right node version.
Begin by opening a terminal, creating a folder for the project and initialising an npm project using yarn init.
mkdir aws-graphql-cdk && cd "$_" && yarn init -y
Next, in order to write our code in typescript, we need to create a tsconfig.json
. We are going to use tsconfig.json to truly take this project to the moon 🚀.
Run npx tsconfig.json
and select ‘node’ from the technology list.
Finally, add the packages for typescript.
yarn add typescript @types/node
Now we’re ready to start coding! 💻
Building the GraphQL Lambda
First, install the apollo-server-lambda and graphql packages to create our GraphQL server with.
yarn add apollo-server-lambda graphql
Next, add a new file in src/graphql-lambda/index.ts
and create a GraphQL server. This is using ApolloServer to create a simple server that exposes a ‘hello’ query. Then finally we export the lambda handler as the AWS specific integration exposed on the ApolloServer.
import { ApolloServer, gql } from 'apollo-server-lambda'const typeDefs = gql`
type Query {
hello: String
}
`const resolvers = {
Query: {
hello: () => 'Hello from the CDK and typescript lambda!'
}
}const server = new ApolloServer({
typeDefs,
resolvers,
introspection: true,
})export const handler = server.createHandler()
Building AWS Resources with the CDK
First, install the core, apigateway and lambda aws cdk packages.
yarn add @aws-cdk/core @aws-cdk/aws-apigateway @aws-cdk/aws-lambda-nodejs
Then, create a cloudformation stack to house all of the resources. Add the following code to src/cdk/stack.ts
to setup an empty stack.
import { App, Construct, Stack } from '@aws-cdk/core'const serverlessGraphQLStack = (construct: Construct) => {
const stack = new Stack(construct, 'ServerlessGraphQLStack')
}const app = new App()
serverlessGraphQLStack(app)
Now we’re ready to start adding the resources!
Add the following files for the lambda and api gateway.
src/cdk/lambda.ts
import { Runtime } from '@aws-cdk/aws-lambda'
import { NodejsFunction } from '@aws-cdk/aws-lambda-nodejs'
import { Construct } from '@aws-cdk/core'
import path from 'path'export const nodeJSLambda = (stack: Construct, id: string, codePath: string) => {
return new NodejsFunction(stack, id, {
entry: path.join(__dirname, codePath),
runtime: Runtime.NODEJS_14_X,
bundling: {
externalModules: ['aws-sdk']
}
})
}
This function returns the NodejsFunction
construct. We pass in a path to where the handler is, and set some defaults like the node version. The externalModules
property allows you to specify modules that are already available in the runtime to avoid unnecessary duplication of modules in the bundle. As this is a lambda, the aws-sdk
module is already available.
src/cdk/restApi.ts
import { RestApi, LambdaIntegration } from '@aws-cdk/aws-apigateway'
import { IFunction } from '@aws-cdk/aws-lambda'
import { Construct } from '@aws-cdk/core'export const restApi = (stack: Construct, id: string) => {
return new RestApi(stack, id, {
restApiName: id
})
}export const lambdaIntegration = (handler: IFunction) => new LambdaIntegration(handler)
This exposes two functions; one returns the RestApi
construct and the other returns a LambdaIntegration
construct to connect the lambda to the api.
To add these to the stack it’s a simple case of importing and invoking them!
import { App, Construct, Stack } from '@aws-cdk/core'
import { nodeJSLambda } from './lambda'
import { restApi, lambdaIntegration } from './restApi'const serverlessGraphQLStack = (construct: Construct) => {
const stack = new Stack(construct, 'ServerlessGraphQLStack')
const graphQLServerLambda = nodeJSLambda(stack, 'GraphQLServerLambda', '../graphql-lambda/index.ts') const graphQLServerRestApi = restApi(stack, 'GraphQLServerRestApi') const graphQLServerLambdaIntegration = lambdaIntegration(graphQLServerLambda)}const app = new App()
serverlessGraphQLStack(app)
Next, we need to integrate the lambda with the api gateway so that we can invoke it over a /graphql
endpoint.
Add a resource to the api, and then add a GET
and POST
method, passing in the integration defined earlier.
import { App, Construct, Stack } from '@aws-cdk/core'
import { nodeJSLambda } from './lambda'
import { restApi, lambdaIntegration } from './restApi'const serverlessGraphQLStack = (construct: Construct) => {
const stack = new Stack(construct, 'ServerlessGraphQLStack')
const graphQLServerLambda = nodeJSLambda(stack, 'GraphQLServerLambda', '../graphql-lambda/index.ts') const graphQLServerRestApi = restApi(stack, 'GraphQLServerRestApi') const graphQLServerLambdaIntegration = lambdaIntegration(graphQLServerLambda) const graphQL = graphQLServerRestApi.root.addResource('graphql')
graphQL.addMethod('GET', graphQLServerLambdaIntegration)
graphQL.addMethod('POST', graphQLServerLambdaIntegration)}const app = new App()
serverlessGraphQLStack(app)
Finally we need to create a cdk.json
config file to tell the cdk where our app lives!
{
"app": "npx ts-node --prefer-ts-exts ./src/cdk/stack.ts"
}
That’s the stack complete, now we’re ready to deploy!
Deploying to AWS with the CDK
In order to deploy to AWS via the CDK we need the following tools installed:
- aws-cli →
aws.amazon.com/cli
- aws-cdk cli →
npm i -g aws-cdk
(This tutorial used v1.119 of aws-cdk and 2.2.20 of the aws-cli)
Once they are installed, make sure you have also configured your AWS environment in the aws-cli
by running aws configure
and entering your access keys.
The next step is to bootstrap the AWS account being used, which will automatically provision resources the CDK needs to deploy the stack.
cdk bootstrap aws://<aws_account_id>/<aws_region>
E.g. cdk bootstrap aws://03057671267/us-east-1
Finally, add a script to the package.json
that can be run to deploy the stack.
{
"name": "aws-graphql-cdk",
"version": "1.0.0",
"license": "MIT",
"dependencies": {
"@aws-cdk/aws-apigateway": "^1.119.0",
"@aws-cdk/aws-lambda-nodejs": "^1.119.0",
"@aws-cdk/core": "^1.119.0",
"@types/node": "^16.6.2",
"apollo-server-lambda": "^3.1.2",
"graphql": "^15.5.1",
"typescript": "^4.3.5"
},
"scripts": {
"cdk": "cdk synth && cdk deploy --require-approval never",
}
}
This will synthesise your CDK code into a CloudFormation template and then deploy it, authorising the changes.
Now comes the exciting part! Run yarn cdk
and watch the magic happen. Once it’s finished deploying, you will get an output on the terminal of the endpoint to invoke the api gateway on.
You can test it out in Postman by sending a POST
request to https://<your_endpoint>/graphql
with the following body:
query {
hello
}
E.g. https://mu1iuirj4j.execute-api.us-east-1.amazonaws.com/prod/grahpql
You should receive a successful response back ✅
{
"data": {
"hello": "Hello from the CDK and typescript lambda!"
}
}
Bonus! Running the stack locally
It’s possible to run the stack locally using sam-beta-cdk if you don’t have access to an AWS environment whilst developing. To install it, run the following command: (you’ll need homebrew 🍺 for this, and you may need to run brew tap aws/tap
beforehand)
brew install aws-sam-cli-beta-cdk
Then add another script to your package.json
to run it locally.
{
"name": "aws-graphql-cdk",
"version": "1.0.0",
"license": "MIT",
"dependencies": {
"@aws-cdk/aws-apigateway": "^1.119.0",
"@aws-cdk/aws-lambda-nodejs": "^1.119.0",
"@aws-cdk/core": "^1.119.0",
"@types/node": "^16.6.2",
"apollo-server-lambda": "^3.1.2",
"graphql": "^15.5.1",
"typescript": "^4.3.5"
},
"scripts": {
"cdk": "cdk synth && cdk deploy --require-approval never",
"dev": "cdk synth && sam-beta-cdk local start-api"
}
}
This will again synthesise your cdk code into a CloudFormation template and then run it locally using sam-beta-cdk.
Before running it locally, you will need docker 🐳 installed and running.
Run yarn dev
and then you can test it out with Postman by sending the same POST
request as earlier, just swapping out your AWS url with the url output in the terminal, normally http://localhost:3000/graphql
.
You should receive the same successful response back ✅
{
"data": {
"hello": "Hello from the CDK and typescript lambda!"
}
}
Wrap up
That’s it! The CDK constructs used in this article take away a lot of the hassle in deploying your code to AWS, and they’re available for other languages such as Python and Go!
Happy building! 🏠