TypeScript-first middleware for AWS lambdas
Tags: aws, typescript
Published: 2020-08-18 16:43:57

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](https://dbartholomae.github.io/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:

```ts
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?

```ts
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:

```ts
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](https://dbartholomae.github.io/lambda-middleware/). There’s also [an article](https://startup-cto.net/simplify-aws-lambda-functions-with-middleware/) about different ways to set up middleware for lambdas and their pros and cons