DEV Community

Cover image for TypeScript-first middleware for AWS lambdas
Daniel Bartholomae
Daniel Bartholomae

Posted on

TypeScript-first middleware for AWS lambdas

Over the last years, TypeScript has become more and more prevalent in the NodeJS backend world. This also applies for AWS lambdas, especially since AWS is also pushing TypeScript with AWS CDK.
For AWS lambda middleware, there is a strongly-typed alternative now: lambda-middleware. It allows you to wrap your handler with middleware and get TypeScript to tell you about how the middleware changes your event and response objects.

A simple example

Let’s say you have the following lambda function:

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

export async function add(event: APIGatewayEvent, context: Context): Promise<APIGatewayProxyResult> {
  const { a, b } = JSON.parse(event.body ?? "{}");
  const sum = a + b;
  return {
    statusCode: 200,
    body: JSON.stringify({ result: sum })
  }
}

This code isn’t really typesafe yet: JSON.parse will return any, but there is no guarantee that a and b are actually set. It is also a mix between the actual business logic and code that transforms from and to http requests.

What does the code look like with lambda-middleware?

import { APIGatewayProxyResult, APIGatewayEvent, Context } from "aws-lambda";
import { IsNumber } from "class-validator";
import { compose } from "@lambda-middleware/compose";
import { classValidator } from "@lambda-middleware/class-validator";
import { errorHandler } from "@lambda-middleware/http-error-handler";
import { jsonSerializer } from "@lambda-middleware/json-serializer";

class Summands {
  @IsNumber()
  a!: number;

  @IsNumber()
  b!: number;
}

async function sum({
  body: { a, b },
}: {
  body: Summands;
}): Promise<{ result: number }> {
  return { result: a + b };
}

export const add: (
  event: APIGatewayEvent,
  context: Context
) => Promise<APIGatewayProxyResult> = compose(
  errorHandler(),
  jsonSerializer(),
  classValidator({ bodyType: Summands })
)(sum);

The actual business logic is now extracted into a function called sum that doesn’t really know anything about http requests. The classValidator and jsonSerializer middlewares take care of converting from and to http requests, while validating the input at the same time.

Where does the strict typing come in? Let’s see the error messages we get if we introduce some otherwise easy-to-miss bugs.

If we remove jsonSerializer() from the list of middlewares, we get the following error:

Error:(31, 3) TS2345: Argument of type '({ body: { a, b }, }: { body: Summands; }) => Promise<{ result: number; }>' is not assignable to parameter of type 'PromiseHandler<WithBody<APIGatewayProxyEvent, Summands>, APIGatewayProxyResult>'.
  Type 'Promise<{ result: number; }>' is not assignable to type 'Promise<APIGatewayProxyResult>'.
    Type '{ result: number; }' is missing the following properties from type 'APIGatewayProxyResult': statusCode, body

TypeScript warns us that we never converted our business-logic result to an APIGatewaryProxyResult.

Let’s now change our handler:

async function sum({
  body: { a, b, c},
}: {
  body: { a: number, b: number, c: number };
}): Promise<{ result: number }> {
  return { result: a + b + c};
}

This gives us the following error message:

Error:(31, 3) TS2345: Argument of type '({ body: { a, b, c }, }: { body: { a: number; b: number; c: number; }; }) => Promise<{ result: number; }>' is not assignable to parameter of type 'PromiseHandler<WithBody<APIGatewayProxyEvent, Summands>, APIGatewayProxyResult>'.
  Types of parameters '__0' and 'event' are incompatible.
    Type 'WithBody<APIGatewayProxyEvent, Summands>' is not assignable to type '{ body: { a: number; b: number; c: number; }; }'.
      Types of property 'body' are incompatible.
        Property 'c' is missing in type 'Summands' but required in type '{ a: number; b: number; c: number; }'.

TypeScript saved us from using a property c that we never validated is on the request.

Next steps

If you want to read more about the lambda middleware itself, best have a look at its documentation. There’s also an article about different ways to set up middleware for lambdas and their pros and cons

Top comments (0)