Deploying a TypeScript + Node AWS Lambda Function with Serverless

Serverless plugins make developing Lambda apps with TypeScript painless (no webpack required!)

This guide assumes you have TypeScript and Node installed as well as an AWS account. We'll be using serverless to set up our development environment and to package our app for deployment.

From mkdir to Deploy

Let's get the boring stuff out of the way first. We'll install serverless and configure our AWS credentials. After that we're ready for our first deploy.

First, let's install the serverless CLI:

npm i serverless -g

We can now generate the minimum deployable boilerplate for our app:

mkdir node-typescript-lambda && cd node-typescript-lambda
serverless create --template aws-nodejs

Most importantly, we now have a simple Lambda function in handlers.js and a serverless.yml which will tell serverless how to deploy our function to AWS.

Now let's configure our AWS credentials so we can deploy our function. If you already have an access key and secret access key in ~/.aws/credentials then you can skip this step. First, sign in to the AWS console. At the top right, click on your name to toggle the dropdown and navigate to "My Security Credentials. Then on the left navigation pane, click on "Users" and select a user with appropriate access. Click on the "Security credentials tab" to bring up the pane we're interested in. Under the section "Access keys" click "Create Acccess key"

AWS Security credentials tab with arrow pointing to the 'Create access key' button.

Take note of the two keys you've generated and then run the following command in your terminal, replacing YOUR_ACCESS_KEY and YOUR_SECRET_KEY with their actual values.

serverless config credentials --provider aws --key YOUR_ACCESS_KEY --secret YOUR_SECRET_KEY

You can now choose to deploy with serverless deploy, but be warned this will count as a request made on your AWS account, and may incur a charge.

Serverless plugins

We're going to be using two serverless plugins. serverless-offline will allow us to test changes to our app locally. serverless-plugin-typescript will automatically compile our TypeScript files down to JavaScript both when we're developing locally and when we deploy our app.

To keep your deployment payload small, it's important to declare these plugins as dev dependencies, otherwise serverless will install them before deploying them, increasing your lambda function's size from <1kB to >30MB

npm init
npm i serverless-offline serverless-plugin-typescript --save-dev

We'll run our local environment with severless offline start but first we we need to edit serverless.yml to include the plugins we just installed.

Anywhere at the lowest indendation level in serverless.yml include these lines.

plugins:
  - serverless-plugin-typescript
  - serverless-offline

If you were to remove serverless-plugin-typescript you would be able to start your dev server, but we'll first need a tsconfig.json to tell the TypeScript compiler what to do. While we're at it, let's install the type definitions for aws-lambda.

tsc --init
npm i @types/aws-lambda --save-dev

You can configure your tsconfig.json to your personal preference, the only requirement is: "rootDir": "./" so that the compiler knows to look for .ts files in the current directory. If you choose to move all your .ts files to a new folder called ./src, for example, make sure to change the setting to "rootDir": "./src". You can also set "es6" as your compilation target because Lambda uses Node 6, which supports it. I've included a sample tsconfig.json here:

Adding Types

Our handler.js could use a makeover. Let's rename it to handlers.ts and run our development server with serverless offline start.

We'll first import the relevant types from 'aws-lambda' and then add types to each of the function's parameters as well as the response object.

import { Handler, Context, Callback } from 'aws-lambda';

interface HelloResponse {
  statusCode: number;
  body: string;
}

const hello: Handler = (event: any, context: Context, callback: Callback) => {
  const response: HelloResponse = {
    statusCode: 200,
    body: JSON.stringify({
      message: Math.floor(Math.random() * 10)
    })
  };

  callback(undefined, response);
};

export { hello }

With these type definitions, our editor will loudly warn us when we try to add a key that shouldn't exist and provide suggestions based off the type definitions.

Visual Studio Code showing an error for a key that shouldn't exist on the response object and suggestions for a key that can exist on the response object.](/

We're almost ready to deploy this test function, but first let's exclude node_modules from our serverless.yml so we don't deploy aws-lambda and all of its dependencies.

Anywhere at the lowest indendation level of serverless.yml add the following lines:

package:
  exclude:
    - node_modules/**/*
  include:
    handler.ts

Making Our Lambda Function an Endpoint

By editing our serverless.yml once more, we can configure our Lambda function to be a visitable URL with AWS APIGateway.

functions:
  hello:
    handler: handler.hello

    events:
      - http:
          path: hello
          method: get

By adding the last four lines, we configure our hello Lambda function to expose the path /hello to an HTTP GET request. If we run serverless deploy now it will generate a URL that you can visit in your browser.

The output of the serverless CLI showing us the URL endpoint for our lambda function