Photo by Javier Allegue Barros on Unsplash
Creating a SAM starter project for TypeScript in 5min
Are you building serverless apps on AWS using SAM cli + TypeScript? Tired of doing the same repetitive tasks for every new component? This might help!
The problem
I have written code in many programming languages but probably the one I am most comfortable these days is TypeScript. I ❤️ types support, access to all NPM wonders and the fact that I can write both frontend and backend applications with it.
For that reason I like to use it whenever I can - and recently that means building serverless applications on AWS Lambda functions with AWS SAM (Serverless Application Model).
With AWS SAM you can quickly create serverless applications based on templates provided for each language, and deploy them using SAM Templates (based on AWS CloudFormation). Still, AWS SAM does not provide support for TypeScript which forces developers to run the same configuration steps to add support for TypeScript in every new project they create.
This is not necessarily hard to do, it's just boring! 😫 For that reason I decided to create a starter project on GitHub that you can:
- easily clone to start immediately writing your application
- follow the README file with detailed steps on how to add TypeScript support on a SAM Node.js template
The perfect storm
While I was writing this blog post I spotted this thread on Twitter between current and former AWS employees which means that I am not alone. I hope this helps them as well as you.
The solution
Create a serverless application using SAM
Let's start by creating a NodeJs SAM application using the default template. This will provide us with the default folder structure and all the dependencies required to use SAM.
sam init --name hello-world --runtime nodejs14.x --dependency-manager npm --app-template hello-world
⚠️ Make sure to install NodeJs and SAM CLI before running this command otherwise it will fail
Open the project folder in your favorite IDE
SAM is a command-line tool that supports developers while building, testing and deploying their serverless application to AWS. So there aren't many requirements around the code editor you use. You can either use VS Code, Sublime or Notepad, it doesn't really matter.
⚠️ Due to personal preference I will use Visual Studio Code for the rest of this document.
cd hello-world
code .
Initialize npm
This step will create a package.json
file in your root folder. You can create this file manually but running the command will save us some time by setting up some default configurations.
npm init
⚠️ You need to answer some questions but you can select all the default values.
Install dev dependencies
By installing the following dependencies we are adding support for Typescript, Webpack and Webpack plugin for SAM. We will also add node and lambda types support.
⚠️ Having tried multiple approaches in the past this one seems to be the most effective to me and I've adopted it some time ago.
npm install webpack webpack-cli typescript ts-loader aws-sam-webpack-plugin @types/aws-lambda @types/node --save-dev
Configure Webpack for SAM
Webpack is essentialy a static module bundler but it also provides support for plugins that can perform a wide range of tasks such as asset management, bundle optimization and environment variables injection.
In this case, I use Webpack to configure the AWS SAM Plugin and use it to generate my lambda function(s) every time I build the project.
First, create a webpack.config.js
file on the root folder.
touch webpack.config.js
Copy the following configuration to webpack.config.js. This will configure webpack to use SAM.
const path = require("path");
const AwsSamPlugin = require("aws-sam-webpack-plugin");
const awsSamPlugin = new AwsSamPlugin();
module.exports = {
// Loads the entry object from the AWS::Serverless::Function resources in your
// SAM config. Setting this to a function will
entry: () => awsSamPlugin.entry(),
// Write the output to the .aws-sam/build folder
output: {
filename: (chunkData) => awsSamPlugin.filename(chunkData),
libraryTarget: "commonjs2",
path: path.resolve(".")
},
// Create source maps
devtool: "source-map",
// Resolve .ts and .js extensions
resolve: {
extensions: [".ts", ".js"]
},
// Target node
target: "node",
// AWS recommends always including the aws-sdk in your Lambda package but excluding can significantly reduce
// the size of your deployment package. If you want to always include it then comment out this line. It has
// been included conditionally because the node10.x docker image used by SAM local doesn't include it.
// externals: process.env.NODE_ENV === "development" ? [] : ["aws-sdk"],
// Set the webpack mode
mode: process.env.NODE_ENV || "production",
// Add the TypeScript loader
module: {
rules: [{ test: /\.tsx?$/, loader: "ts-loader" }]
},
// Add the AWS SAM Webpack plugin
plugins: [awsSamPlugin]
};
⚠️ This configuration is an exact copy/paste from the AWS SAM Plugin documentation so I will not go deep into the details
Add Typescript configuration file
Typescript expects a configuration file - tsconfig.json
- on the root folder of the project. We can do this by using the Typescript command-line.
The following command will create a tsconfig.json
file with all the supported configuration options.
tsc --init
You can adapt the Typescript configuration file to your own preferences but mine looks like this.
{
"compilerOptions": {
"target": "es2015",
"module": "commonjs",
"sourceMap": true,
"rootDir": "./hello-world",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true
}
}
⚠️ This configuration specifies that the root directory is ./hello-world
but if you have multiple Lambda functions you would probably prefer to have a src
folder with all functions in different sub-directories.
If that is the case you should update the rootDir
options with ./src
.
Configure build scripts
Last thing we need to do is to configure the build scripts to use webpack instead of the default npm scripts. For simplicity sake I only have support for build
and watch
commands but you can extend this (e.g. with tests).
Open package.json
file and add replace the scripts section with the following.
"scripts": {
"build": "webpack-cli",
"watch": "webpack-cli -w"
},
Update your function code
Now that we have the project configured to support Typescript we only need to replace our Javascript Lambda function with a Typescript one.
Rename hello-world/app.js
to hello-world/app.ts
and update its content with a very basic Typescript code.
import { APIGatewayProxyEvent, APIGatewayProxyResult } from "aws-lambda";
export const lambdaHandler =
async (event: APIGatewayProxyEvent): Promise<APIGatewayProxyResult> => {
return {
'statusCode': 200,
'body': JSON.stringify({
message: 'hello world',
})
}
};
Build and deploy your Lambda function
Deploy the demo to your AWS account using AWS SAM.
npm run build
sam deploy --guided # if running for the first time. Otherwise you can ignore the '--guided' parameter
The npm run build
command will first build the hello-world TypeScript function. Then sam deploy
uses the SAM Template to deploy the resources to your account.
SAM will create an output of the API Gateway endpoint URL for future use in our load tests.
The perfect solution?
Not so fast. As I mentioned before this is a solution that as worked for me, because it allows me to quickly start a project by reusing code.
You could probably implement a similar solution without webpack or you could have a cookiecutter project for SAM like the one for .net as an example.
If you have suggestions on how I can improve my starter project or alternative approaches just create an issue directly on the repo or reach out to me on social media (Twitter or LinkedIn).