DRY Principle in Your AWS SAM Application with Middlewares
Tags: aws, react, javascript, programming
Published: 2023-11-28 16:30:00

To read more articles like this, [visit my blog](https://www.mdfaisal.com/blog)


AWS Lambda is very popular in the serverless world. The ability to write code and not to worry about any infrastructure is great.

AWS SAM (Serverless application model) is a great way to write a complete backend application only with lambda. You just write the functions and define the infrastructure in the code.

But this creates a lot of duplication of logic as each function is written separately. But can we improve our architecture? Let’s find out!

## The Issue with Lambda

Nothing is perfect in this world. And lambda’s are no different. When we use some backend nodejs framework like express we can take care of some of the common tasks in a single place. Like

* Incoming request validation

* Error handling

* Logging

But in aws lambda, we don’t have that privilege. Every lambda we use have the following structure

```js
try{
  // write business logic
}catch(err){
  // handle error
}
```

This is repetitive and boring. How about we find out a way to handle this problem and improve our lambda architecture?

## Here Comes Lambda Middleware

Middleware is a concept mainly familiar to nodejs programmers. Basically, it can intercept every request that comes in. Which is perfect for tackling common issues.

[lambda-middleware](https://dbartholomae.github.io/lambda-middleware/) is a collection of middleware that can be used with your existing lambda architecture.

## Use a single middleware

Error handling is a crucial part of any application. There is [an error handler middleware](https://dbartholomae.github.io/lambda-middleware/packages/http-error-handler/) already written that we can take advantage of.

First, install the dependency.

```js
npm i @lambda-middleware/errorHandler
```
Then use this middleware like the following

```js
import { errorHandler } from "@lambda-middleware/errorHandler";
import { APIGatewayEvent, APIGatewayProxyResult } from "aws-lambda";

async function helloWorld(event: APIGatewayEvent): Promise<APIGatewayProxyResult> {
  // throw error without try catch
  throw new Error("Search is not implemented yet");
}

export const handler = errorHandler()(helloWorld); // <-- see here
```

Using this middleware relieves you from using any kind of try/catch block inside your application. This is huge!

## Use Multiple Middlewares

Now as you can see using just one middleware we have improved our code. Let’s say we want to introduce request validation.

Normally in AWS Lambda, the request body comes inside the `event.body` . We normally parse the request and then check manually if our desired parameters have arrived or not like the following

```js
import { APIGatewayEvent } from 'aws-lambda';

export const handler = async (event: APIGatewayEvent) => {
    try {
      const request = JSON.parse(event.body)
      
      if(!request.name) throw Error('Name not found!')
      
    } catch (err: any) {
        return errorResposnse(err);
    }
};
```


But what if we can do the same thing but in a more declarative way?

Let’s first create a new request class

```js
import { IsNotEmpty } from "class-validator";

class NameRequest {
  
  @IsNotEmpty()
  public name: string;

}
```


In this class, we are using the famous [class-validator](https://github.com/typestack/class-validator) to decorate our request parameters. This way it is more clear how we are going to use our request object and everything.

Then install the following middleware dependency

```js
npm i @lambda-middleware/class-validator
```

Then use it in the lambda like the following

```js
import { classValidator } from '@lambda-middleware/class-validator'
import { APIGatewayEvent, APIGatewayProxyResult } from "aws-lambda";
import NameRequest from './NameRequest'

async function helloWorld(event: { body: NameRequest }): Promise<APIGatewayProxyResult> {
  // body will have the name property here. 
  // No need to check
}

export const handler = classValidator()(helloWorld); // <-- see here
```

## Using Multiple Middlewares

Now we have seen how our code can be improved with the introduction of middleware. What if we want to use 2 middleware at the same time?

Well, there is another package named [compose](https://dbartholomae.github.io/lambda-middleware/packages/compose/) just to do that. The idea is we get a function named `composeHandler` and use that to aggregate multiple lambda middleware.

```js
import { classValidator } from '@lambda-middleware/class-validator'
import { compose } from "@lambda-middleware/compose";
import { errorHandler } from "@lambda-middleware/errorHandler";
import { PromiseHandler } from "@lambda-middleware/utils";
import { APIGatewayEvent, APIGatewayProxyResult } from "aws-lambda";
import NameRequest from './NameRequest'

async function helloWorld(event: { body: NameRequest }): Promise<APIGatewayProxyResult> {
  // body will have the name property here. 
  // No need to check
  // also the errors will be handled automatically!
}

export const handler: ProxyHandler = compose(
  errorHandler(),
  classValidator({
      bodyType: NameRequest
  }))
)(helloWorld);
```

So this is how we can use multiple middlewares.

## Improving this even more!

Now let’s say you are writing hundreds of lambda functions. and from the code, we can still see that we are calling the same `compose` function inside every middleware.

can we reduce that? Let’s first create a utility function that will create a lambda function.

```js
import { composeHandler } from '@lambda-middleware/compose';
import { errorHandler } from './error-handler-middleware';
import { classValidator } from './incoming-request-validator';
import { ClassType } from 'class-transformer-validator';
import {
    APIGatewayEventDefaultAuthorizerContext,
    APIGatewayEventRequestContext,
    APIGatewayProxyEventStageVariables,
    APIGatewayProxyResult,
    Context
} from 'aws-lambda';

type LambdaRequestContext = {
    environment: string;
    authorizer: APIGatewayEventDefaultAuthorizerContext;
};

export const createLambdaHandler = <TRequest extends object, TResponse>(
    requestModel: ClassType<TRequest>,  // the request model
    executeBusinessLogic: (request: TRequest, context: LambdaRequestContext) => Promise<TResponse> // the business logic
) => {
  
    // here we are creating the wrapper for our actual business logic
    const handlerWrapper = async (
        event: {
            body: TRequest;
            stageVariables: APIGatewayProxyEventStageVariables | null;
            requestContext: APIGatewayEventRequestContext;
        },
        context: Context
    ): Promise<APIGatewayProxyResult> => {

        const response = await executeBusinessLogic( event.body, {
            environment: event?.stageVariables?.Environment ,
            authorizer: event?.requestContext?.authorizer
        });

        return {
            headers: {},
            isBase64Encoded: false,
            multiValueHeaders: {},
            statusCode: 200,
            body: JSON.stringify(response)
        };
    };

    return composeHandler(
        errorHandler(),
        classValidator({
            bodyType: requestModel
        }),
        handlerWrapper
    );
};
```

In this function, we are passing 2 things. The first parameter is the type of the request body for the lambda to be used by `class-validator` and the second parameter is the actual business logic for the lambda.

Then we are calling the business logic inside the `executeBusinessLogic` to call the actual logic and return the response.

Now we can use this function to create any lambda we want without the need to duplicate code.

```js

import { createLambdaHandler } from './createLambdaHandler';
import { NameRequest } from './ NameRequest ';

export const handler = createLambdaHandler(NameRequest, async (request, context) => {
    // only care about the business logic here 
});
```

## Final thoughts

Honestly finding out that we can use middleware just changed the game for me. It has improved the code quality to another level.

There are so many things that you can do with this concept. For example, you can write your own middleware too. Because at the end of the day they are just functions. So go ahead!

That’s it for today. Have a Great Day! :D



**Have something to say? Get in touch with me via [LinkedIn](https://www.linkedin.com/in/56faisal/) or [Personal Website](https://www.mdfaisal.com/)**