NestJS based microservice for writing applications that run on AWS Lambda
npm install @klarna/nest-lambda-microservice> Custom transporter implementation for running NestJS based applications on AWS Lambda.

The Nest Lambda Microservice is a custom NestJS transporter solution that enables writing NestJS applications to process events on AWS Lambda.
A Nest Lambda Microservice application is composed of a client, a broker and a server.
The client provider exposes a public method processEvent(event: unknown, context: Context) that is invoked by the Lambda handler.
Based on the event source, the event is mapped into one or multiple messages and published to the broker, e.g. an API Gateway event results in one message, but an SQS Event containing multiple Records, results in multiple messages published separately to the broker.
The client awaits the processing of the messages and assembles a response to be returned by the Lambda handler based on the returned values from the handler that qualified and processed the message.
A sample Lambda application processing SQS events (more examples are available in this folder)
``typescript
/ index.ts /
import { Context } from 'aws-lambda'
import { getLambdaMicroserviceClient } from './microservice'
export const handler = async (event: unknown, context: Context) => {
const client = await getLambdaMicroserviceClient()
return await client.processEvent(event, context)
}
`
`typescript
/ microservice.ts /
import { INestMicroservice } from '@nestjs/common'
import { NestFactory } from '@nestjs/core'
import { MicroserviceOptions } from '@nestjs/microservices'
import { ClientToken, LambdaMicroserviceServer } from '@klarna/nest-lambda-microservice'
import { broker } from './broker'
import { AppModule } from './app.module'
let microservice: INestMicroservice
export const getOrCreateLambdaMicroservice = async () => {
if (!microservice) {
microservice = await NestFactory.createMicroservice
strategy: new LambdaMicroserviceServer({ broker }),
logger: false,
abortOnError: false,
})
await microservice.listen()
}
return microservice
}
export const getLambdaMicroserviceClient = async () => {
return await (await getOrCreateLambdaMicroservice()).resolve(ClientToken)
}
`
`typescript
/ app.module.ts /
import { Module } from '@nestjs/common'
import { ClientsModule } from '@nestjs/microservices'
import { ClientToken, LambdaMicroserviceClient } from '@klarna/nest-lambda-microservice'
import { APP_FILTER, APP_PIPE } from '@nestjs/core'
import { broker } from './broker'
import { BooksController } from './books.controller.ts'
import { BooksService } from './books.service.ts'
import { TransformPipe } from './transform.pipe.ts'
@Module({
controllers: [BooksController],
providers: [
BooksService,
{ provide: APP_PIPE, useClass: TransformPipe }
],
imports: [
ClientsModule.register([{ name: ClientToken, customClass: LambdaMicroserviceClient, options: { broker } }]),
],
})
export class AppModule {}
`
`typescript
/ transform.pipe.ts /
import { ArgumentMetadata, Injectable, PipeTransform } from '@nestjs/common'
@Injectable()
export class TransformPipe implements PipeTransform {
public transform(value: any, _metadata: ArgumentMetadata) {
return this.tryParseJson
}
protected tryParseJson
try {
return JSON.parse(value)
} catch (_error: unknown) {
return value
}
}
}
`
`typescript
/ books.service.ts /
import { Injectable, Scope } from '@nestjs/common'
import { v4 } from 'uuid'
export interface Book {
id: string
title: string
}
@Injectable({ scope: Scope.DEFAULT })
export class BooksService {
protected books = new Map
public async saveNewBook(title: string) {
const book = { id: v4(), title }
this.books.set(book.id, book)
return book
}
}
`
`typescript
/ create-book.dto.ts /
import { IsString } from 'class-validator'
export class CreateBookDto {
@IsString()
public title: string
}
`
`typescript
/ books.controller.ts /
import { Controller } from '@nestjs/common'
import { MessagePattern, Payload } from '@nestjs/microservices'
import { SqsRecordPattern, UsePartialPatternMatch } from '@klarna/nest-lambda-microservice'
import { CreateBookDto } from './create-book.dto.ts'
@Controller()
@UsePartialPatternMatch()
export class BooksController {
constructor(protected readonly booksService: BooksService) {}
@MessagePattern
public async createBook(@Payload('body') sqsRecordBody: CreateBookDto) {
// The transform pipe parsed the serialised sqs record body
await this.booksService.saveNewBook(sqsRecordBody.title)
}
}
`
`typescript`
@Controller()
export class Controller {
@MessagePattern({ action: 'foo', resourceId: '1' })
public processFoo() {}
}
option to the MessagePattern or mark the entire controller to use partial matches using the provided decorator.
`typescript
@Controller()
export class Controller {
@MessagePattern({ action: 'foo' }, { partialMatch: true }) // Applies partial match on a specific handler only
public processFoo() {}
}
``typescript
@UsePartialPatternMatch() // Applies partial match on all controller handlers
@Controller()
export class Controller {
@MessagePattern({ action: 'foo' })
public processFoo() {}
}
`$3
When no controller qualifies the message pattern, a lookup for a "catch-all" handler identified by * pattern qualifier is performed.
If no such handler is defined, the message is rejected and the Lambda event processing fails.`typescript
@Controller()
export class Controller {
@MessagePattern({ action: 'foo' }) // Qualifies messages with { action: 'foo' } patters
public processFoo() {}
@MessagePattern('*') // Qualifies messages with patterns other than { action: 'foo' }
public processBarBazAndCo() {}
}
`Message Processing
The Nest Lambda Microservice supports sync/async Request/Response message style (see more details on the NestJS documentation page).The two inputs into the Lambda function can be accessed in the NestJS application using the dependency injection:
`typescript
import { Controller } from '@nestjs/common'
import { Ctx, MessagePattern, Payload } from '@nestjs/microservices'
import { LambdaContext } from '@klarna/lambda-microservice'@Controller()
export class Controller {
@MessagePattern('*')
public processAnyMessage(
@Payload() inboundMessage: unknown,
@Ctx() context: LambdaContext
) {
console.log(inboundMessage) // The message as mapped from the input lambda event
console.log(context.getLambdaInvocationContext()) // The Lambda function context object
}
}
`$3
The incoming API Gateway Event is mapped to the message pattern using the following logic:`typescript
interface ApiGatewayPattern {
httpMethod: string
resource: string
queryStringParameters: Record | null
pathParameters: Record | null
}
`The message payload is the original API Gateway event.
For more details see this example.
$3
The incoming custom event is any event used to manually invoke the AWS Lambda`typescript
type CustomEventPattern = '*'
`The message payload is the original payload the Lambda was invoked with.
For more details see this example.
$3
The incoming Event Bridge event is mapped to the message pattern using the following logic:`typescript
interface EventBridgePattern {
source: string
detailType: string // The Event Bridge event detail-type
detail: JSONValue // The Event Bridge event detail
}
`The message payload is the original EventBridge event.
For more details see this example.
$3
The S3 event is mapped using the following logic:`typescript
interface S3RecordPattern {
eventName: string
bucketName: string
objectKey: string
}
`The message payload is a Record from the original S3 event.
For more details see this example.
$3
The SNS events are mapped using the event attributes`typescript
interface SnsRecordPattern {
[key: string]: string | number
}
`The message payload is the SNSMessage.
For more details see this example.
$3
The SQS events are mapped using the event attributes`typescript
interface SqsRecordPattern {
[key: string]: string | number
}
``The message payload is a Record from the original SQS event.
For more details see this example.
See our guide on contributing.
See our changelog.
Copyright © 2024 Klarna Bank AB
For license details, see the LICENSE file in the root of this project.
[ci-image]: https://img.shields.io/badge/build-passing-brightgreen?style=flat-square
[ci-url]: https://github.com/klarna-incubator/TODO
[license-image]: https://img.shields.io/badge/license-Apache%202-blue?style=flat-square
[license-url]: http://www.apache.org/licenses/LICENSE-2.0
[klarna-image]: https://img.shields.io/badge/%20-Developed%20at%20Klarna-black?style=flat-square&labelColor=ffb3c7&logo=klarna&logoColor=black
[klarna-url]: https://klarna.github.io