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

👉 Access GitHub repo here

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.

Screenshot 2022-01-26 at 21.26.01.png

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).

Did you find this article valuable?

Support Tiago Barbosa by becoming a sponsor. Any amount is appreciated!