Peter Morlion logo


NestJS & AWS Lambda Without HTTP

At a current client, we’re looking to move (most of) our AWS Lambda functions to NestJS. The company has built up an extensive collection of Lambda functions and it’s time to bring some structure and similarity in them.

But NestJS is geared towards incoming HTTP calls. This is fine if your Lambda function is behind an API Gateway, but is it possible to use NestJS if your Lambda function should be triggered by SNS events?


Those who know me, know I’m not a fan of forcing each team and each project in a company to follow the same structure in their code and project organization.

There is never a one-size-fits-all way of organizing code that works for every team. But that’s a whole different discussion.

So why would I be OK with using NestJS for all our AWS Lambda functions? Because it’s just about the framework, not about the details. We’re going to use NestJS, which recommends a certain way of programming. But it doesn’t mean we need to write all our code in the same way. There are even functions that won’t be written with NestJS because they’re so small it would be overkill.

What is NestJS?

NestJS is another JavaScript framework, yes. And while I don’t care for JS framework discussions, it does provide us some great benefits.

Our Lamba functions were previously written in all kind of styles, depending on who wrote it. Often, they weren’t very testable.

NestJS gives us a structure and some guidance that allows for clean code, decoupled components and easier testability.

What’s nice is that is uses Express, which we were already using.

Are there other frameworks out there that provide similar or better benefits? Probably. But NestJS will do the job just nicely.

To HTTP or not to HTTP?

Most of our Lambda functions are triggered by a HTTP call. If you’re not familiar with AWS, you should know that Lambda functions can be started by a variety of triggers: a HTTP call, a record being added to a database, a message being sent to AWS’s Simple Notification Service (SNS),…

In most cases, we use AWS API Gateway, meaning that our Lambda functions are triggered by some HTTP call. The API Gateway then forwards the call to the relevant Lambda function.

However, we have some that are only triggered by other types of events. For example, we have a function that is subscribed to an SNS topic. If you don’t know SNS, think of it as a simple messaging system: someone sends a message to a Topic and other components can subscribe to these topics.

So how can we get NestJS to run without the context of a HTTP call?

NestJS Without HTTP

In “regular” NestJS, you would bootstrap your application and then “listen” for HTTP calls:

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  await app.listen(3000);

In a Lambda function, you can use the serverless-http package to wrap your NestJS:

async function bootstrap() {
  const app = await NestFactory.create(AppModule, new ExpressAdapter(expressApp));
  return app;

// then, in your handler function:
const app = await bootstrap();
const appHandler = serverlessHttp(app);
return await appHandler(event, context);

But that doesn’t work if there won’t be any HTTP calls coming in.

Instead, we can write our Lambda as we would normally and in our handler function we can bootstrap our NestJS application, get the provider we need, and pass on the incoming data:

async function bootstrap() {
  const app = await NestFactory.createApplicationContext(AppModule);
  return app;

export async function handler(event, context) {
  const app = await bootstrap();
  const appService = app.get(AppService);
  await appService.doSomething(event);

That’s basically it. Instead of having NestJS listen for incoming HTTP calls, we use NestJS for all the other goodies it provides (like dependency injection, separation of concerns and testability) and just get the service we need and pass in the required data.

Update: Thank you Akshay for pointing out that this is now documented in the (great) NestJS documentation, on the page about standalone applications.

Update2: Thank you Elvys Cruz for pointing out that there is now even more documentation. Next to the docs mentioned by Akshay, there is also an FAQ about Serverless that contains a piece about standalone applications.

8 Responses

  1. Thank you for this article. What we see if we invoke the function manually or via SQS, that the function does not finish execution on its own. It basically stays open, as if the nest server would keep listening for incoming request, although we have followed the bootstrapping you showed

    export async function handler(event, context) {
    const app = await bootstrap();
    const appService = app.get(AppService);
    await appService.doSomething(event);

    1. Hi Florian,

      I’m assuming this means you don’t see the “END” log entry in CloudWatch? Because in my case I see this within milliseconds after the “START” log entry. This indicates to me that the Lambda has finished executing.

      It’s odd because as far as I can tell, we’re not “starting” NestJS, or telling it to listen for incoming requests. We’re basically just using NestJS for the dependency injection features.

      A possible explanation could be that you are calling the “.listen” method on the NestJS app somewhere, or that some promise isn’t resolving. That would also mean your Lambda times out, right?

      Maybe try to reduce your Lambda to the absolute minimum. Maybe just log the event in the “doSomething” function. Let me know if you find anything.

  2. I tried this, but when I run the app using `sam local invoke` none of my dependencies are injected (when using constructor injection). Any ideas?

    1. Hm, I’m not using the sam CLI tool, so I can’t really pitch in. Maybe post it on StackOverflow. If you get an answer that works, I’d love to hear about it!

Leave a Reply

Your email address will not be published. Required fields are marked *