'NestJS and Serverless - handler 'handler' is not a function

I am trying to implement NestJS as a AWS Serverless function using serverless-framework.

I was following this official documentation and my code is exactly as in the documentation. However when I launch it I get the error Failure: offline: handler 'handler' in [..] is not a function.

If I go into my compiled source code of main.js and change the line exports.handler = handler; to module.exports.handler = handler; it starts working.

I also tried to do change the code in main.ts to accommodate this but it does not help since webpack is compiling it differently then.

// main.ts
const handler ...;
module.exports.handler = handler;

Here is my main.ts

import { ValidationPipe } from '@nestjs/common';
import { NestFactory } from '@nestjs/core';
import { Callback, Context, Handler } from 'aws-lambda';
import serverlessExpress from '@vendia/serverless-express';
import { AppModule } from './app.module';

let server: Handler;

async function bootstrap(): Promise<Handler> {
  const app = await NestFactory.create(AppModule);
  app.useGlobalPipes(
    new ValidationPipe({
      whitelist: true,
    }),
  );
  await app.init();

  const expressApp = app.getHttpAdapter().getInstance();
  return serverlessExpress({ app: expressApp });
}

export const handler: Handler = async (event: any, context: Context, callback: Callback) => {
  server = server ?? (await bootstrap());
  return server(event, context, callback);
};

Here is my serverless.yml

service:
  name: serverless-example

plugins:
  - serverless-offline

provider:
  name: aws
  runtime: nodejs12.x

functions:
  main:
    handler: dist/main.handler
    events:
      - http:
          method: ANY
          path: /
      - http:
          method: ANY
          path: '{proxy+}'

Here is my webpack.config.js

return {
  ...options,
  externals: [],
  output: {
    ...options.output,
    libraryTarget: 'commonjs2',
  },
  // ... the rest of the configuration
};

And lastly here is my tsconfig.json

{
  "compilerOptions": {
    "module": "commonjs",
    "declaration": true,
    "removeComments": true,
    "emitDecoratorMetadata": true,
    "experimentalDecorators": true,
    "allowSyntheticDefaultImports": true,
    "target": "es2017",
    "sourceMap": true,
    "outDir": "./dist",
    "baseUrl": "./",
    "incremental": true,
    "skipLibCheck": true,
    "strictNullChecks": false,
    "noImplicitAny": false,
    "strictBindCallApply": false,
    "forceConsistentCasingInFileNames": false,
    "noFallthroughCasesInSwitch": false,
    "esModuleInterop": true
  }
}

Am I missing some config on webpack? Or maybe change in the typescript config files? I have no idea and documentation says that it should just work, however it does not.

Theoretically all I need is it to be module.exports.handler = handler instead of exports.handler = handler in my compiled file because as I said I did change it and it started to work properly.

This is the interim fix I'm using but obviously this is wrong way of approaching it.

"build": "nest build --webpack && sed -i 's/exports.handler = handler;/module.exports.handler = handler;/g' dist/main.js",


Solution 1:[1]

I resolved this by creating ensuring that the webpack output has a libraryTarget of commonjs2. Create (or edit) a webpack.config.js file in the root of the project with the following content ensuring that we set the libraryTarget to commonjs2:

module.exports = (options) => {
  return {
    ...options,
    output: {
      ...options.output,
      libraryTarget: 'commonjs2',
    },
  };
};

In my serverless.yml I have the following defined for my lambda function:

...

functions:
  example:
    handler: "./dist/apps/example/main.js.handler"
...

Please note that main.js.handler should be changed if your entrypoint/function is different. For example if it was entrypoint this string would be dist/apps/example/main.js.entrypoint

Solution 2:[2]

I found the solution :) for the same problem but with serverless-plugin-typescript with nx workspace

there is all about tsconfig with module: commonjs

"compilerOptions": {  
  "module": "commonjs",
},

and then I see in ./build folder

...
exports.main = main;

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Cal
Solution 2