Dominik Marciniszyn

Backend Developer

Feb 23, 2021 in Development

Lambda in TypeScript with Serverless Framework

Introduction

Hello everyone. Today I would like to show you how to create your own lambda function in typescript. To achieve this I’ll use the serverless framework and AWS cloud. But first, let’s start with some theory to explain what is serverless. Without further ado, let’s get into the topic.

What is Serverless?

Serverless is the cloud service where developer focuses only on creating logic. The serverless does not require server maintenance. When developer creates the serverless solution, he must not worry about creating servers on physical machines, configuring networks or updating operating system on those servers.  Those tasks are done by cloud providers. The most popular cloud providers nowadays are Amazon, Google and Microsoft. Those companies provides lots of different serverless solutions. In the serverless approach we pay only for what we use.

Serverless framework

We now know what serverless is, but the question is, how to get started with it? Last year I learnt the serverless framework. The technology to create serverless solutions. Serverless framework allows to develop and deploy serverless applications to AWS, Azure, GCP and other clouds. This framework offers an easy way to deploy applications. It’s just one command to deploy app to the AWS or other clouds. The serverless framework offers also local testing of the solutions. For example, you can invoke locally your lambda function and check what will happen, if the lambda responds with a valid response. The serverless framework has tons of plugins from the community. A plugin is custom JavaScript code that creates new or extends existing commands within the Serverless Framework. I used some of them during work. Okay, now that we know what is serverless and the tool we can use for its creation - it’s time for a code example!

Create lambda function with Serverless Framework

In this section I will show how to prepare simple lambda written in typescript. The lambda is from a project I’m working on with my friends. First things first - what do we need?

  • At least node version 12
  • serverless framework
  • typescript 4 

To install node visit https://nodejs.org/en/ and select the version you want. Then we need to install serverless framework. If you have installed node then you can simply use the following command:

npm install -g serverless

This command installs the serverless framework globally in your system. Thanks to that you will be able to use serverless command from shell. You will need this. Last thing you need is typescript, the command is similar to serverless one:

npm install -g typescript

We have all required technologies to start. First we need to create the tsconfig file to setup the typescript project. To achieve this we can use tsc —init command. It will generate a well described typescript config file. You can read it and select what you want to setup. In my case I’ve created the following tsconfig file:

{
  "compilerOptions": {
    "target": "es2020",
    "lib": [ "es2020" ],
    "outDir": "./dist",
    "module": "commonjs",
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true,
    "sourceMap": true,
    "strict": true,
    "noImplicitAny": true,
    "noUnusedLocals": true,
    "noUnusedParameters": true,
    "noImplicitReturns": true,
    "esModuleInterop": true,
    "allowSyntheticDefaultImports": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,
    "removeComments": true,
    "resolveJsonModule": true,
    "declaration": true,
    "strictNullChecks": true
  },
  "include": ["./src"],
  "exclude": ["./dist"]
}

Then we need a package.json. We can generate it via npm init command. Fill the asked fields as you want. For me it is:

{
  "name": "pa-serverless",
  "version": "1.0.0",
  "description": "PompApp serverless backend",
  "main": "index.ts",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "repository": {
    "type": "git",
    "url": “git”+yourUrl”
  },
  "keywords": [
    "backend",
    "serverless",
    "PompApp",
    "typescript",
    "nodejs"
  ],
  "author": "",
  "license": "ISC",
  "bugs": {
    "url": “yourUrl/issues”
  },
  "homepage": “yourUrl#Readme”
}

Then I install dev dependencies I like to use in projects. They are very useful. I’m using commitlint to keep commits convention across the project. Eslint is useful in case of code linting. Husky lib allows to define checks on pre commit for example. Jest is my favourite test framework, containing everything that is needed to perform tests. Serverless typescript plugin is for creating lambdas in typescript. Serverless offline plugin is for offline testing. This plugin simulates lambdas but on your local machine. You can reach your local lambda from the postman for example. Below the full list of my dev dependencies.

"devDependencies": {
    "@commitlint/cli": "^11.0.0",
    "@commitlint/config-conventional": "^11.0.0",
    "@types/aws-lambda": "^8.10.72",
    "@types/node": "^14.14.31",
    "@typescript-eslint/eslint-plugin": "^4.15.1",
    "@typescript-eslint/parser": "^4.15.1",
    "eslint": "^7.20.0",
    "eslint-config-prettier": "^7.2.0",
    "eslint-plugin-jest": "^24.1.5",
    "eslint-plugin-node": "^11.1.0",
    "eslint-plugin-prettier": "^3.3.1",
    "eslint-plugin-security": "^1.4.0",
    "eslint-plugin-you-dont-need-lodash-underscore": "^6.11.0",
    "husky": "^4.3.6",
    "jest": "^26.6.3",
    "lint-staged": "^10.5.4",
    "npm-check-updates": "^11.1.4",
    "prettier": "^2.2.1",
    "serverless-offline": "^6.8.0",
    "serverless-plugin-typescript": "^1.1.9",
    "typescript": "^4.1.5"
  }

Disclaimer: For now the hooks in husky 5.0 are not working so I placed husky in the .ncurc file.

Now I will present configuration of some of the dev dependencies. I would like to create separate blog post about that but I decided to place it here. It can help others to prepare their own template for a project I hope. So let’s start with commitlint. It is a pretty simple config to keep your commits clean. In my case the types used are: build, chore, feat, fix, refactor, revert, test and deps.

"commitlint": {
    "extends": [
      "@commitlint/config-conventional"
    ],
    "rules": {
      "type-case": [2, "always", "lower-case"],
      "type-enum": [2, "always", ["build", "chore", "feat", "fix", "refactor", "revert", "test", "deps"]],
      "scope-empty": [2, "always"],
      "type-empty": [2, "never"],
      "subject-empty": [2, "never"],
      "subject-min-length": [2, "always", 10],
      "subject-full-stop": [2, "never", "."],
      "subject-case": [2, "always", "lower-case"]
    }
  }

Next, we setup the husky. I use three hooks - pre-commit, pre-push and commit-msg. Pre commit lints the code and checks which package could be updated to a newer version. On pre push there are running tests.

"husky": {
    "hooks": {
      "pre-commit": "npm run lint -s && npm run check",
      "pre-push": "npm test --NODE_ENV=test",
      "commit-msg": "commitlint -E HUSKY_GIT_PARAMS"
    }
  }

Last thing we need to setup are eslint rules. This configuration file will help us keep the code clean.

{
  "root": true,
  "parser": "@typescript-eslint/parser",
  "parserOptions": {
    "project": "./tsconfig.json"
  },
  "extends": [
    "plugin:you-dont-need-lodash-underscore/compatible",
    "eslint:recommended",
    "plugin:@typescript-eslint/eslint-recommended",
    "plugin:@typescript-eslint/recommended",
    "plugin:@typescript-eslint/recommended-requiring-type-checking",
    "plugin:jest/recommended",
    "plugin:jest/style",
    "plugin:security/recommended",
    "plugin:prettier/recommended",
    "prettier/@typescript-eslint",
    "prettier"
  ],
  "plugins": [
    "@typescript-eslint",
    "prettier",
    "jest",
    "security",
    "node"
  ],
  "rules": {
    "prettier/prettier": [
      "error",
      {
        "singleQuote": true,
        "semi": true,
        "printWidth": 120
      }
    ],
    "@typescript-eslint/explicit-function-return-type": [
      "error",
      {
        "allowExpressions": true
      }
    ],
    "@typescript-eslint/no-explicit-any": "error",
    "@typescript-eslint/no-unused-vars": "error",
    "@typescript-eslint/camelcase": "off",
    "@typescript-eslint/no-empty-interface": "off",
    "@typescript-eslint/semi": "warn",
    "@typescript-eslint/no-use-before-define": "error",
    "@typescript-eslint/no-namespace": "warn",
    "security/detect-object-injection": "warn",
    "no-var": "error",
    "no-console": "warn",
    "camelcase": "off",
    "curly": "warn",
    "eqeqeq": "warn",
    "no-throw-literal": "warn",
    "semi": "off",
    "no-empty": "warn",
    "no-unused-expressions": ["error", { "allowTernary": true }],
    "no-use-before-define": "off",
    "@typescript-eslint/naming-convention": [
      "error",
      { "selector": "variableLike", "format": ["camelCase"] },
      {
        "selector": "variable",
        "format": ["camelCase", "UPPER_CASE"]
      },
      {
        "selector": "variable",
        "types": ["boolean"],
        "format": ["PascalCase"],
        "prefix": ["is", "should", "has", "can", "did", "will"]
      },
      {
        "selector": "typeParameter",
        "format": ["PascalCase"],
        "prefix": ["T"]
      }
    ]
  },
  "ignorePatterns": [
    "./package.json",
    "./package-lock.json"
  ]
}

Now when we have everything prepared, we can start working on our lambda. First we need to install serverless framework. To do this use command:

npm install serverless -g

Next, we need to define serverless.yml file to store serverless configuration. The configuration contains a few sections. First we need to define service name and the framework version. Then there is a section of plugins for serverless framework we would like to use. In my case it will be serverless-offline and the serverless-plugin-typescript. In the next section we need to define provider we will use. For me it is AWS, the runtime for my lambda will be nodejs in version 14. I declared stage dev and the region as eu-central-1. Those settings you can define as you want. For example for GCP and lambda in python. Last section contains definition of my ping lambda. I allocate 256 MB RAM for the lambda. The lambda is accessible via GET request on /ping path. The whole configuration is presented below.

service: pompapp-serverless

frameworkVersion: '2'

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

provider:
  name: aws
  runtime: nodejs12.x
  stage: dev
  region: eu-central-1

functions:
  ping:
    handler: src/functions/ping.ping
    description: Lambda function just for article purposes - to show sls offline
    memorySize: 256
    events:
      - http:
          path: /ping
          method: get
          cors: true

The configuration is done, so it’s time to create code for our lambda. The code will be simple, we would like to return the message from lambda. But first, we need to add aws-sdk dependency to our project. Open the package.json and add this code. We also need the aws-lambda types in the devDependencies.

“devDependencies”: {
  "@types/aws-lambda": "^8.10.72",
}

"dependencies": {
    "aws-sdk": "2.848.0"
}

We are using typescript so good practise is to make types for all elements. Our lambda will return the response, so we can create type for the response. We use combination of type and interface - the interface HeaderOption used by LambdaResponse type.

export type LambdaResponse = {
  headers: HeaderOption;
  statusCode: number;
  body: string;
};

interface HeaderOption {
  'Access-Control-Allow-Origin': string;
};

Now its time to implement the lambda. We have aws-sdk so we can also type all things. In response we place message - “lambda is alive!”. Lambda must be asynchronous, in other way the response will never appear.

import { APIGatewayProxyEvent } from 'aws-lambda';
import { LambdaResponse } from '../types/types';

export const ping = async (event: APIGatewayProxyEvent): Promise<LambdaResponse> => {
  return {
    headers: {
      'Access-Control-Allow-Origin': '*',
    },
    statusCode: 200,
    body: JSON.stringify('Lambda is alive!'),
  }
};

Now we can use curl or postman to see if we are able to obtain the response from lambda. Run ‘sls offline’ to run lambda without deploying it. Next we can use curl to invoke the lambda: “curl -X GET http://localhost:3000/dev/ping”. Worth to mention is that when you run sls offline the terminal will show all defined lambdas with its paths. The result of curl is: "Lambda is alive!"% Our lambda is working!

Short info how to deploy lambda to AWS

Of course you want to deploy lambda to the AWS. To do this you need AWS account you can create at: https://aws.amazon.com/. Then you need to generate keys to access the cloud. If you setup everything, deploying lambda is very easy with serverless framework. You need to use command sls deploy --aws-profile your-profile-name. The serverless framework will do everything for you and deploy the lambda to the cloud.

Summary

This article shows how to define typescript project for aws lambda. This is simple example but it’s good point to start build your own serverless solutions. Serverless solutions are very popular in last times so it is worth to learn. If you want to know more please visit: https://www.serverless.com/. I hope you enjoyed the article and see you in the next one!

Do you need more information about this topic?

Schedule a call with our developers

Let's talk!

Back to top