Debugging SAM Based TypeScript Lambdas

https://flic.kr/p/Em7wAx

If you’ve had to create serverless apps or services in 2020 using the AWS Serverless Application Model (SAM) and AWS Lambda, then there’s one problem you’ve run into — setting breakpoints and debugging it the old fashioned way. The AWS toolkit currently does not support debugging typescript with Webstorm. This article helps you set up debugging in your TypeScript Lambda repo of choice.

The starter has .idea folder which when opened in your favourite Jetbrains IDE, ends up automatically inheriting the configuration as well. Please thank the kind soul who decided to do this.

  • Install AWS Toolkit for Webstorm. This should give you the SAM Lambda local debug option.

Instructions on installing the toolkit can be found at https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/setup-toolkit.html

If you notice, by default as of now (August 23rd, 2020) — a lambda handler written in TypeScript will not be picked up by default. Webstorm’s debugger and the AWS toolkit need some pushing along to let us set breakpoints and debug ts files.

The workaround is to have your handler exposed via a wrapper function. I’m not entirely sure why this works or why the Webstorm debugger needs it a certain way but it does work.

Once again the link working boilerplate, check out https://github.com/rtre84/typescript-aws-sam

Assuming your lambda handler is called handler, expose it at the bottom of the index.ts or app.ts file similar to what’s below.

export const lambdaHandler = (event, context) =>
wrapper(handler)(event, context, {});

Check the file at https://github.com/rtre84/typescript-aws-sam/blob/master/hello-world/app.ts for a working example.

import {
APIGatewayProxyEvent,
APIGatewayProxyHandler,
Callback,
Context,
Handler
} from "aws-lambda";
import { parse } from "querystring";

export type LambdaFnWrapper = () => (fn: Handler) => LambdaProxyHandler;
export type LambdaProxyHandler = (
event: APIGatewayProxyEvent,
context: Context,
callback: Callback
) => Promise<APIGatewayProxyHandler> | void;

const lambdaFnWrapper: LambdaFnWrapper = (): ((
fn: Handler
) => LambdaProxyHandler) => {
return (fn: Handler): LambdaProxyHandler => {
return (
event: APIGatewayProxyEvent,
context: Context,
callback: Callback
): Promise<APIGatewayProxyHandler> | void => {
// TODO add any pre processing of incoming requests
if (
event.headers["Content-Type"] === "application/x-www-form-urlencoded"
) {
(event as any).body = parse((event as any).body);
}

return fn(event, context, callback);
};
};
};

export const httpFn: (fn: Handler) => LambdaProxyHandler = lambdaFnWrapper();

const middleware: (fn: Handler) => LambdaProxyHandler = (
fn: Handler
): LambdaProxyHandler => {
return (
event: APIGatewayProxyEvent,
context: Context,
callback: Callback
): Promise<APIGatewayProxyHandler> | void => {
(context as any).myName = "Miro";

return fn(event, context, callback);
};
};

export const wrapper: (fn: Handler) => LambdaProxyHandler = (
fn: Handler
): LambdaProxyHandler => httpFn(middleware(fn));

The wrapper function can be found at https://github.com/rtre84/typescript-aws-sam/blob/master/hello-world/mw/mw.ts

  • Right-click the directory that contains your TypeScript Lambda source code and choose Mark directory as. In the submenu that follows, choose Resource Root. This should solve most problems of the lambda handler not being found.

The Debugger Configuration

Debugger Config
Debugger Config
Lambda Debugger Configuration

Select the Cloudformation template in the template field. The dropdown should populate and your Lambda function should now be selectable.

I prefer to use --debug flags in the SAM CLI configuration so the output is verbose.

Lambda Debugger Configuration SAM CLI Section

The AWS section should look similar to what’s below. If you’re trying this behind a company VPN or via a federated account, you might need to generate temporary AWS credentials.

Lambda Debugger Configuration AWS Connection

In the input field, copy-paste what’s below to be your event template. This is only valid if you’re using the boilerplate mentioned above. If you are setting up your own repo, this might not be relevant.

{
"body": "{\"test\":\"body\"}",
"resource": "/{proxy+}",
"path": "/path/to/resource",
"httpMethod": "POST",
"queryStringParameters": {
"foo": "bar"
},
"pathParameters": {
"proxy": "path/to/resource"
},
"stageVariables": {
"baz": "qux"
},
"headers": {
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8",
"Accept-Encoding": "gzip, deflate, sdch",
"Accept-Language": "en-US,en;q=0.8",
"Cache-Control": "max-age=0",
"CloudFront-Forwarded-Proto": "https",
"CloudFront-Is-Desktop-Viewer": "true",
"CloudFront-Is-Mobile-Viewer": "false",
"CloudFront-Is-SmartTV-Viewer": "false",
"CloudFront-Is-Tablet-Viewer": "false",
"CloudFront-Viewer-Country": "US",
"Host": "1234567890.execute-api.{dns_suffix}",
"Upgrade-Insecure-Requests": "1",
"User-Agent": "Custom User Agent String",
"Via": "1.1 08f323deadbeefa7af34d5feb414ce27.cloudfront.net (CloudFront)",
"X-Amz-Cf-Id": "cDehVQoZnx43VYQb9j2-nvCh-9z396Uhbp027Y2JvkCPNLmGJHqlaA==",
"X-Forwarded-For": "127.0.0.1, 127.0.0.2",
"X-Forwarded-Port": "443",
"X-Forwarded-Proto": "https"
},
"requestContext": {
"accountId": "123456789012",
"resourceId": "123456",
"stage": "prod",
"requestId": "c6af9ac6-7b61-11e6-9a41-93e8deadbeef",
"identity": {
"cognitoIdentityPoolId": null,
"accountId": null,
"cognitoIdentityId": null,
"caller": null,
"apiKey": null,
"sourceIp": "127.0.0.1",
"cognitoAuthenticationType": null,
"cognitoAuthenticationProvider": null,
"userArn": null,
"userAgent": "Custom User Agent String",
"user": null
},
"resourcePath": "/{proxy+}",
"httpMethod": "POST",
"apiId": "1234567890"
}
}

In the end, you should have a toolbar at the top that looks something like this.

Debugger Toolbar

Congratulations! You should now have a working debug configuration. Set a breakpoint in a .ts file of choice and debug away by clicking the debug icon.

Debug icon

Appendix

Another TypeScript Lambda Starter: https://github.com/mir4ef/aws-lambda-typescript-starter

Flickr Image Source

Hi there! I’m a software developer based out of Boston, MA. Worked at DSW, T-Mobile, HMH, Wellington Management and Thesys CAT to name a few.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store