Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to debug in VS Code a local AWS Lambda function with API Gateway written in TypeScript?

We are about to start working with Lambda functions.
We have that technology constraint that we have to use TypeScript.
I want to be able to debug my ts file in VS Code when the related endpoint is called from Postman.

So, we have the following development environment:

  • Windows 10
  • Docker for Windows (with Hyper-V not with WSL 2)
  • TypeScript 4.1
  • Node 12.17
  • SAM CLI 1.13.2

I've used sam init with the Hello World template to generate the initial folder structure.
I've enhanced it (mostly based on this article) to work with TypeScript.

Folder structure

.
├── template.yaml
├── .aws-sam
├── .vscode
|   └── launch.json
├── events
├── hello-world
|   ├── dist
|       ├── app.js
|       └── app.js.map
|   ├── src  
|       └── app.ts
|   ├── package.json
|   └── tsconfig.json

template.yaml

AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: >
  LambdaWithApiGateWayDebug

  Sample SAM Template for LambdaWithApiGateWayDebug

Resources:
  HelloWorldFunction:
    Type: AWS::Serverless::Function 
    Properties:
      CodeUri: hello-world/dist
      Handler: app.lambdaHandler
      Runtime: nodejs12.x
      Events:
        HelloWorld:
          Type: Api
          Properties:
            Path: /hello
            Method: get

tsconfig.json

{
  "compilerOptions": {
    "target": "ES2020",
    "module": "commonjs",
    "sourceMap": true,
    "outDir": "./dist",
    "strict": true,
    "noImplicitAny": true,
    "esModuleInterop": true,
    "sourceRoot": "./src",
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules", "**/*.spec.ts"]
}

package.json

{
  "name": "hello_world",
  "version": "1.0.0",
  "description": "hello world sample for NodeJS",
  "main": "app.js",
  "repository": "https://github.com/awslabs/aws-sam-cli/tree/develop/samcli/local/init/templates/cookiecutter-aws-sam-hello-nodejs",
  "scripts": {
    "compile": "tsc",
    "start": "sam local start-api -t ../template.yaml -p 5000 -d 5678"
  },
  "dependencies": {
    "@types/aws-lambda": "^8.10.64",
    "@types/node": "^14.14.10",
    "aws-sdk": "^2.805.0",
    "source-map-support": "^0.5.19",
    "typescript": "^4.1.2"
  }
}

app.ts

import { APIGatewayProxyEvent, APIGatewayProxyResult } from "aws-lambda";

export const lambdaHandler = async (event: APIGatewayProxyEvent): Promise<APIGatewayProxyResult> => {
    const queries = JSON.stringify(event.queryStringParameters);
    return {
      statusCode: 200,
      body: `Queries: ${queries}`
    }
}

app.js

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.lambdaHandler = void 0;
const lambdaHandler = async (event) => {
    const queries = JSON.stringify(event.queryStringParameters);
    return {
        statusCode: 200,
        body: `Queries: ${queries}`
    };
};
exports.lambdaHandler = lambdaHandler;
//# sourceMappingURL=app.js.map

app.js.map

{"version":3,"file":"app.js","sourceRoot":"./src/","sources":["app.ts"],"names":[],"mappings":";;;AAEO,MAAM,aAAa,GAAG,KAAK,EAAE,KAA2B,EAAkC,EAAE;IAC/F,MAAM,OAAO,GAAG,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,qBAAqB,CAAC,CAAC;IAC5D,OAAO;QACL,UAAU,EAAE,GAAG;QACf,IAAI,EAAE,YAAY,OAAO,EAAE;KAC5B,CAAA;AACL,CAAC,CAAA;AANY,QAAA,aAAa,iBAMzB"}

launch.json

{
    "version": "0.2.0",
    "configurations": [
        {
            "type": "node",
            "request": "attach",
            "name": "attach Program",
            "port": 5678,
            "address": "localhost",
            "localRoot": "${workspaceFolder}/hello-world/dist",
            "remoteRoot": "/var/task",
            "protocol": "inspector",
            "sourceMaps": true,
            "smartStep": true,
            "outFiles": ["${workspaceFolder}/hello-world/dist"]
        }
    ]
}

As you can see:

  • My lambda function is defined in the hello-world/src/app.ts
  • It is complied with commonJs and ES2020 target to hello-world/dist/app.js with sourcemap
  • The template exposes that handler which in located under the hello-world/dist via the localhost:5000/hello endpoint
  • The debugger is listening on the port 5678

So, when I call npm run start then it prints the following output:

> [email protected] start C:\temp\AWS\LambdaWithApiGateWayDebug\hello-world
> sam local start-api -t ../template.yaml -p 5000 -d 5678

Mounting HelloWorldFunction at http://127.0.0.1:5000/hello [GET]
You can now browse to the above endpoints to invoke your functions. You do not need to restart/reload SAM CLI while working on your functions, changes will be reflected instantly/automatically. You only need to restart SAM CLI if you update your AWS SAM template
2020-12-08 11:40:48  * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)

When I make a request against the endpoint via Postman then the console is extended with the following text:

Mounting C:\temp\AWS\LambdaWithApiGateWayDebug\hello-world\dist as /var/task:ro,delegated inside runtime container
START RequestId: 04d884cf-fa96-4d58-b41c-e4196e12de13 Version: $LATEST
Debugger listening on ws://0.0.0.0:5678/d6702717-f291-42cd-8056-22b9f029f4dd
For help, see: https://nodejs.org/en/docs/inspector

When I attach my VS Code to the node process then I can only debug the app.js and not the app.ts.
End of the console log:

Debugger attached.
END RequestId: 04d884cf-fa96-4d58-b41c-e4196e12de13
REPORT RequestId: 04d884cf-fa96-4d58-b41c-e4196e12de13  Init Duration: 0.12 ms  Duration: 7064.19 ms    Billed Duration: 7100 ms        Memory Size: 128 MB     Max Memory Used: 128 MB
No Content-Type given. Defaulting to 'application/json'.
2020-12-08 11:40:58 127.0.0.1 - - [08/Dec/2020 11:40:58] "GET /hello HTTP/1.1" 200 -

Question

What should I change to be able to debug my app.ts instead of app.js?

like image 762
Peter Csala Avatar asked Dec 08 '20 10:12

Peter Csala


2 Answers

As it turned out I have made two tiny mistakes:

sourceRoot

I've set sourceRoot inside tsconfig.json which is unnecessary in this case. include is enough:

{
  "compilerOptions": {
    "target": "ES2020",
    "module": "commonjs",
    "sourceMap": true,
    "outDir": "./dist",
    "strict": true,
    "noImplicitAny": true,
    "esModuleInterop": true,
    // "sourceRoot": "./src",
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules", "**/*.spec.ts"]
}

BreakPoints

I've set two breakpoints: one inside app.js and another inside app.ts.
As it turned out if app.js does have a breakpoint then the other one will not be trigger.

debugging js

So after I've removed the breakpoint from app.js then the debugger stopped inside the app.ts.

debugging ts

like image 149
Peter Csala Avatar answered Oct 09 '22 16:10

Peter Csala


I wrote a medium article explaining how to create, invoke and debug a TypeScript lambda function. Please for more details check the following link.

Requirements

  • SAM CLI
  • Docker
  • Node
  • Visual studio code

1- Create a nodejs12x;

  1. Create a nodejs12x lambda;

  2. Install TypeScript: npm i -g typescript

  3. Initialize typescript: tsc --init (It will create the tsconfig.json file)

  4. Replace the created tsconfig.json file with the following code:

    {
    "compilerOptions": {
    "module": "CommonJS",
    "target": "ES2017",
    "noImplicitAny": true,
    "preserveConstEnums": true,
    "outDir": "./built",
    "sourceMap": true
    },
    "include": ["handler/**/*"],
    "exclude": ["node_modules", "**/*.spec.ts"]
    }
    
  5. Delete app.js file;

  6. Create your lambda in TypeScript code inside of handler folder (you need o create it):

    import { 
     APIGatewayProxyEvent, 
     APIGatewayProxyResult 
    } from "aws-lambda";
    
    export const lambdaHandler = async (
      event: APIGatewayProxyEvent
    ): Promise<APIGatewayProxyResult> => {
      const queries = JSON.stringify(event.queryStringParameters);
      return {
        statusCode: 200,
        body: `Queries: ${queries}`
      }
    }
    
  7. Adapt the template.yaml. Change the CodeUri path of your lambda: CodeUri: hello-world/built

  8. Install the needed node packages: npm install typescript @types/aws-lambda @types/node -save-dev

  9. Package json:

    {
    "name": "typescript_lambda",
    "version": "1.0.0",
    "description": "hello world sample for TypeScript",
    "main": "app.js",
    "repository": "https://github.com/jafreitas90/AWS",
    "author": "Jorge Freitas",
    "license": "JorgeFreitas Ltd :)",
    "dependencies": {
    },
    "scripts": {
      "compile": "tsc"
    },
    "devDependencies": {
      "@types/aws-lambda": "^8.10.71",
      "@types/node": "^14.14.22",
      "aws-sdk": "^2.815.0",
      "typescript": "^4.1.3"
    }
    }
    
  10. npm install

  11. npm run compile

  12. Set a breakpoint in your lambda function (TypeScript code). On the left panel select Debug and then Start debugging (green button on top). And that's it :)

  13. Project structure

enter image description here

Please find more details in my tutorial.

source code

like image 42
Jorge Freitas Avatar answered Oct 09 '22 16:10

Jorge Freitas