Elevating Your AWS Lambda Game With Middy And TypeScript
Tags: typescript, programming, aws, webdev
Published: 2024-02-28 13:02:00

## Introduction
In the ever-evolving landscape of AWS Lambda development, efficiency is key. Picture this: you're building serverless functions, but the process feels a bit like assembling a puzzle without all the pieces. That's where Middy steps in as the missing link.

AWS Lambda, while revolutionary, can pose challenges in terms of code organization, reusability, and overall development simplicity. Without the right tools, navigating these challenges might feel like traversing uncharted territory.

Enter Middy, the middleware framework designed to streamline and enhance AWS Lambda functions. Middy acts as your development ally, addressing the pain points of Lambda development with its middleware architecture. In this blogpost you'll discover how Middy turns these challenges into opportunities for a more efficient and enjoyable serverless development experience. 

> _⚠️ This is the second part of the series focused on AWS Lambda and serverless. You don't need the first part, but if you are here you will probably like it. Here is the link:https://cloudnature.net/blog/nextlevel-serverless-development-with-sst-and-typescript._

---

## Setting the Stage
![Lambda execution environment lifecycle](https://content.cloudnature.net/images/4d52a2bc-614f-4210-9d70-36b42dd92992)

AWS Lambda functions, the backbone of serverless computing, offer scalable and cost-effective execution. Before diving into the power of Middy, it's crucial to grasp the fundamentals of Lambda functions.

AWS Lambda allows you to run code without provisioning or managing servers, responding to events while automatically managing compute resources. You write the code, AWS handles the rest. Fantastic, right? However, like any tech, there are a few aspects to be mindful of.

Here's a simple handler code snippet:
```
export const handler = async (event, context) => {
  console.log("EVENT: \n" + JSON.stringify(event, null, 2));
  return context.logStreamName;
};
```
Lambda functions respond to various events: S3, SNS, SQS, HTTP from API Gateway, and more. In this code we are printing the event that the AWS Lambda function *handled*. 
Lambda development isn't always a smooth sail. Finding and addressing these challenges is crucial for achieving a robust and efficient serverless architecture. Let's explore challenges:

* **Input Logging**: debugging based on received events
* **Output Logging**: ensuring the response is correct
* **Body Parsing**: yep, JSON.parse(event.body) for HTTP events
* **HTTP Body Response**: parsing responses with statusCode and message
* **CORS Headers**: adding them in HTTP response
* **Error Handling**: ah, the inevitable pain point (I can feel you pain)
* **Event Validation**: ensuring events meet expected criteria
* **Event Body Typing**: especially in TypeScript, right?

Solving these challenges often involves developing your Lambda handler wrapper: wrapping in try...catch, logging events, handling errors, and reshaping responses. Well, I did all of this until I stumbled upon THE middleware solution: **Middy**.

---

## Introducing Middy
![How Middy works](https://content.cloudnature.net/images/6fd05598-2b6c-429c-baf2-40789c99e0de)

Middy, a game-changer in Lambda development, introduces a middleware architecture that streamlines your serverless journey. Think of middleware as a series of functions that intercept the flow of data between the initial request and the final response. In the context of AWS Lambda, Middy seamlessly integrates into the execution flow, allowing you to augment, modify, or validate both the incoming events and outgoing responses. 
Each middleware handles a specific concern, contributing to a modular and maintainable codebase. This design facilitates the addition or removal of middleware based on your specific requirements.

Let's revisit some of the challenges we identified earlier and discover how Middy rises to the occasion:

* **Input Logging**: Middy has a dedicated middleware for that.
* **Output Logging**: There's a middleware designed to handle that seamlessly.
* **Body Parsing**: Yes, you guessed it, there's a middleware for parsing HTTP event bodies.
* **HTTP Body Response**: Once again, Middy steps in with a dedicated middleware.

Now that we understand the workings of AWS Lambda and the capabilities Middy brings to the table, let's explore how we can combine them to overcome real-world challenges in serverless development.

---

## Getting Started with Middy and TypeScript
Getting Middy up and running is a breeze. Follow these simple steps to integrate Middy into your AWS Lambda environment:

```
npm install --save @middy/core
npm install --save-dev @types/aws-lambda
```

For typescript I found this issue, which is literally what we need (after a few twiks 🔨)
🔗 https://github.com/middyjs/middy/issues/316

Our Middyfy wrapper sets the stage, providing a structured approach to typing events and handling requests. Now, let's extend it to accommodate essential Middy middleware:
```
import middy, { type MiddyfiedHandler } from '@middy/core'
import type { APIGatewayProxyEvent, APIGatewayProxyResult, Context, Handler as AWSHandler, SNSEvent } from 'aws-lambda'
import { type Entity } from 'dynamodb-onetable'
import { type OneField, type Paged } from 'dynamodb-onetable/dist/mjs/Model'

// Event is an APIGatewayProxyEvent with a typed body, pathParameters and queryStringParameters which depends on http-json-body-parser & json-schema-to-ts
// queryStringParameters and multiValueQueryStringParameters is non-nullable as we use http-event-normalizer
export interface Event<TBody, TPathParameters, TQueryStringParameters>
  extends Omit<APIGatewayProxyEvent, 'body' | 'pathParameters' | 'queryStringParameters'> {
  waitTimestamp: string | number | Date
  body: TBody
  pathParameters: TPathParameters
  queryStringParameters: TQueryStringParameters
  multiValueQueryStringParameters: NonNullable<APIGatewayProxyEvent['multiValueQueryStringParameters']>
}

// We are making use of http-response-serializer, so our body type can either be an Entity, an Array<Entity> or a string
interface Result extends Omit<APIGatewayProxyResult, 'body'> {
  body:
  | Entity<Record<string, OneField>>
  | Paged<Entity<Record<string, OneField>>>
  | string
  | Record<string, unknown>
}

// Handler type which gives us proper types on our event based on TBody and TPathParameters which are JSON schemas
export type Handler<TBody = void, TPathParameters = void, TQueryStringParameters = void> = AWSHandler<
Event<TBody, TPathParameters, TQueryStringParameters>,
Result
>

interface RequestSchema {
  properties?: {
    body?: Record<string, unknown> | null
    pathParameters?: Record<string, unknown> | null
    queryStringParameters?: Record<string, unknown> | null
  }
}

export const middyfy = (
  handler: Handler<never, never, never>,
  requestSchema: RequestSchema | null = null
): MiddyfiedHandler<Event<never, never, never>, Result, Error, Context> => {
  const wrapper = middy(handler)
	
	// Attach desired Middy middlewares here
  wrapper.use(/* middleware 1 */);
  wrapper.use(/* middleware 2 */);
  // ... add more as needed
	
  return wrapper
}
```

Dive into the Middy middleware ecosystem and choose the ones that align with your Lambda function's requirements. Whether it's input logging, output logging, handling HTTP responses... Middy has you covered.

And this is our sample lambda function:

```
import { middyfy, type Handler } from '@core/libs/middyWrapper'
import type { FromSchema } from 'json-schema-to-ts'

export const bodySchema = {
  type: 'object',
  properties: {
    subject: { type: 'string', maxLength: 100 },
    content: { type: 'string', maxLength: 100 }
  },
  required: ['subject', 'content'],
  additionalProperties: false
} as const

export const schema = {
  type: 'object',
  properties: {
    body: bodySchema
  }
} as const

const main: Handler<FromSchema<typeof bodySchema>, void, void> = async (event) => {
  return {
    statusCode: 200,
    body: { ...event }
  }
}

export const handler = middyfy(main, schema)
```

Did you copy everything? Perfect, because now we are going to add middlewares and get our hands dirty 😈

---

## Leveraging Middy's Middleware
Ready for the exciting part? Follow these simple steps to introduce essential middlewares (not all of them, there are a lot) and witness the transformation.

Let's start by installing some middlewares. Open your terminal and run:
```
npm install @middy/http-error-handler
npm install @middy/http-json-body-parser
npm install @middy/http-response-serializer
npm install @middy/validator
npm install @middy/validator/transpile
```

Now, import them into your Middyfy wrapper code:
```
import httpErrorHandlerMiddleware from '@middy/http-error-handler'
import httpJsonBodyParserMiddleware from '@middy/http-json-body-parser'
import httpResponseSerializerMiddleware from '@middy/http-response-serializer'
import validatorMiddleware from '@middy/validator'
import { transpileSchema } from '@middy/validator/transpile'
```

Let's break it down one middleware at the time:
1. **http-json-body-parser**
```
wrapper
      .use(httpJsonBodyParserMiddleware())
```

2. **validator**: which checks if the event is compliant with the schema we specified in out lambda function
```
  if (requestSchema != null) {
    wrapper.use(validatorMiddleware({ eventSchema: transpileSchema(requestSchema) }))
      .use({
        onError: (request) => {
          const response = request.response
          const error = request.error as any
          if (response.statusCode === 400) {
            response.headers['Content-Type'] = 'application/json'
            response.body = JSON.stringify({ message: response.body, validationErrors: error.cause })
          }
        }
      })
  }
```

3. **http-error-handler**
```
wrapper
      .use(httpErrorHandlerMiddleware({}))
```

4. **http-response-serializer**
```
wrapper
      .use(
      httpResponseSerializerMiddleware({
        serializers: [
          {
            regex: /^application\/json$/,
            serializer: ({ body }) => JSON.stringify(body)
          }
        ],
        defaultContentType: 'application/json'
      })
    )
```

With less than 100 lines of code, your Lambda functions are now robust and production-ready.

---

## Considerations
![Logs for input and output Middy middleware](https://content.cloudnature.net/images/cdb84a8f-6450-4953-bb38-de3d13bf3a6a)
Various middlewares showcased in the image above illustrates the power of Middy in action. From logging input and output to enhancing security headers and serializing responses, Middy simplifies complex processes into an elegant solution.

Having incorporated Middy into the production environment for my newsletter, I've witnessed a noticeable improvement in debugging and monitoring. The once complex task has become more streamlined and less prone to messy complications.

A particularly noteworthy aspect is the ability to use a unified interface for input validation and TypeScript typing. This seemingly simple feature significantly elevates the developer experience.

I really like the idea, and I plan to use Middy more in my future projects. Now that I have a new and ready-to-use library for my Middy wrapper, it's even more exciting! 🤩

---

## Future Trends

The future of AWS Lambda development with Middy holds more and more possibilities as the Serverless adoption goes on. The increasing adoption of serverless architecture has attracted attention, leading to AWS and FourTheorem sponsorship which signifies the growing significance of Middy in the serverless ecosystem.

As serverless computing continues to gain traction, more developers are embracing the simplicity and efficiency it offers. This surge in popularity implies a broader user base for Middy, making it a go-to choice for Lambda development.

The growing user base is likely to result in increased community contributions. This influx of developers brings diverse perspectives and ideas, which may lead to create new and innovative middlewares. Expect a richer ecosystem with a wide array of middleware options for various use cases.

While there is already a lot of HTTP-related middlewares, the future holds the promise of an expanded selection for different AWS services.

Hopefully Middy becomes an integral part of the serverless development landscape, it really deserve it ✌️

---

## Conclusion

In closing, Middy emerges as a game-changer in the world of AWS Lambda development. As we explored Lambda, we encountered challenges like logging and error handling. Middy steps in to effortlessly tackle these hurdles with its modular middleware approach.

Middy isn't just a middleware, it's a tool that simplifies AWS Lambda complexities, taking serverless development to a whole new level.

And there you have it, folks! Do I have your attention now? Get ready because the next article will delve into how I developed my Serverless newsletter infrastructure with SST and Middy (plus a really big Step Function) 💪

If you enjoyed this article, please let me know in the comment section or send me a DM. I'm always happy to chat! ✌️

Thank you so much for reading! 🙏 Keep an eye out for more AWS-related posts, and feel free to connect with me on LinkedIn 👉 https://www.linkedin.com/in/matteo-depascale/.

---

## References
* https://middy.js.org/

> _Disclaimer: opinions expressed are solely my own and do not express the views or opinions of my employer._