Better types for AWS API Gateway Lambda functions

Using TypeScript when writing Lambda functions is an art not properly documented and most people rely on Google searches to find a way to write types for their Lambdas. Unfortunately this leaves a trail of confusing and verbose types being repeated by new comers and professionals alike.

The most common trigger for AWS Lambda is API Gateway and I’ll show how to properly type this kind of Lambda using the most concise method. This is not something brand new as it has always been there in the AWS Lambda types hosted on DefinitelyTyped but from my experience I don’t see many people using it. So I’m documenting my approach here for others to learn.

Before I dive into the types, I want to encourage every developer or engineer to always look at the source code (if available) of anything you add to your programs. This is particularly true for us JS developers, as we add hundreds of stuff from the npm registry. Taking a few minutes to look at the Github source for most of the popular repositories we use will enhance your understanding of them and give you better insights into how everything fits together. That is the approach which led me to look at the Lambda types on DefinitelyTyped and found a better way to type my Lambda functions, making them less verbose.

Anyway, back to the topic at hand. During my quick search to find examples of proxied Lambda functions through API Gateway written in TypeScript, I came up with the following example shown below. Know that there is nothing wrong with the code below. It is perfectly fine and works. However, to me it was too verbose and something that I can’t easily remember from memory and that will slow down my process when I’m in focused on getting work done.

import { APIGatewayProxyResult, APIGatewayEvent, Context } from 'aws-lambda';

export const handler = async (
  event: APIGatewayEvent,
  context: Context
): Promise<APIGatewayProxyResult> => {
  return {
    statusCode: 200,
    headers: { 'Content-Type': 'text/plain' },
    body: `You arrived here using this path: ${event.path}\n`,
  };
};

Looking at the repository for the AWS Lambda types, it became apparent to me that you can consider the APIGatewayProxyResult, APIGatewayEvent, and Context types as primitive types consumed by higher level types. In particular, the APIGatewayProxyHandler type encompasses both the APIGatewayEvent type for events and the APIGatewayProxyResult type for the resulting promise type (the Result).

//Here is a snippet from the repository
/**
 * Works with Lambda Proxy Integration for Rest API or HTTP API integration Payload Format version 1.0
 * @see - https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-develop-integrations-lambda.html
 */
export type APIGatewayProxyHandler = Handler<
  APIGatewayProxyEvent,
  APIGatewayProxyResult
>;

But what about the Context you might ask. How do we type the Context if one is provided to the Lambda function? I’m glad you asked. The answer to that question lies in the Handler type used to compose the APIGatewayProxyHandler type. The handler type already types the Context for us and hence we don’t need to type it any further. Here is the code for the handler:

export type Handler<TEvent = any, TResult = any> = (
  event: TEvent,
  context: Context,
  callback: Callback<TResult>
) => void | Promise<TResult>;

Putting it all together, a rewrite of the above Lambda function will now look like this:

import { APIGatewayProxyHandler } from 'aws-lambda';

export const handler: ApiGatewayProxyHandler = async (event, context) => {
  return {
    statusCode: 200,
    headers: { 'Content-Type': 'text/plain' },
    body: `You arrived here using this path: ${event.path}\n`,
  };
};

Ah, isn’t that much better?