Serverless-express: NestJS example

Created on 25 Jan 2021  路  19Comments  路  Source: vendia/serverless-express

Would it be possible to add sample code for NestJS into https://github.com/vendia/serverless-express/tree/mainline/examples?
For example, how to do something like this with v4.0.0?

import { Server } from 'http'
import { NestFactory } from '@nestjs/core'
import { AppModule } from './app.module'
import { Context, APIGatewayEvent } from 'aws-lambda'
import * as ServerlessExpress from '@vendia/serverless-express'
import express from 'express'
import { ExpressAdapter } from '@nestjs/platform-express'

let lambdaProxy : Server

async function bootstrap() {
  const expressServer = express()
  const app = await NestFactory.create(AppModule, new ExpressAdapter(expressServer))
  await app.init()
  return ServerlessExpress.createServer(expressServer)
}

export const handler = (event : APIGatewayEvent, context : Context) => {

  if (!lambdaProxy) {
    bootstrap().then((server) => {
      lambdaProxy = server
      ServerlessExpress.proxy(lambdaProxy, event, context)
    })
  } else {
    ServerlessExpress.proxy(lambdaProxy, event, context)
  }
}

released

Most helpful comment

Alright, I think I ended up figuring it out. I've updated my repo. If I'm off or missing something let me know!

All 19 comments

Great idea! My file is similar:

lambda.ts

// transpiling https://github.com/claudiajs/claudia/issues/132
// no relative paths https://github.com/claudiajs/claudia/issues/40#issuecomment-218372727
// timeout https://stackoverflow.com/a/54086731
// aws gateway timeout https://github.com/vendia/serverless-express#considerations
// module.exports https://github.com/janaz/lambda-request-handler/pull/6

import type { Handler, Context } from 'aws-lambda';
import { Server } from 'http';
import { createServer, proxy } from 'aws-serverless-express';
import { eventContext } from 'aws-serverless-express/middleware';

import { NestFactory } from '@nestjs/core';
import {
  ExpressAdapter,
  NestExpressApplication,
} from '@nestjs/platform-express';
import { AppModule } from './app/app.module';
import { useGlobal } from './bootstrap';

// https://github.com/Microsoft/TypeScript/issues/13340
import express = require('express');

// NOTE: If you get ERR_CONTENT_DECODING_FAILED in your browser, this
// is likely due to a compressed response (e.g. gzip) which has not
// been handled correctly by aws-serverless-express and/or API
// Gateway. Add the necessary MIME types to binaryMimeTypes below

// https://github.com/awslabs/aws-serverless-express/blob/master/examples/basic-starter/lambda.js
const binaryMimeTypes: string[] = [
  'application/javascript',
  'application/json',
  'application/octet-stream',
  'application/xml',
  'font/eot',
  'font/opentype',
  'font/otf',
  'image/gif',
  'image/heic',
  'image/jpeg',
  'image/png',
  'image/svg+xml',
  'image/webp',
  'text/comma-separated-values',
  'text/css',
  'text/html',
  'text/javascript',
  'text/plain',
  'text/text',
  'text/xml',
];

let cachedServer: Server;

// Create the Nest.js server and convert it into an Express.js server
async function bootstrapServer(): Promise<Server> {
  if (!cachedServer) {
    const expressApp = express();
    const nestApp = await NestFactory.create<NestExpressApplication>(
      AppModule,
      new ExpressAdapter(expressApp),
    );
    nestApp.use(eventContext());

    // custom add global pipes, filters, interceptors, etc.
    useGlobal(nestApp);

    await nestApp.init();
    cachedServer = createServer(expressApp, undefined, binaryMimeTypes);
  }
  return cachedServer;
}

// Export the handler : the entry point of the Lambda function
export const handler: Handler = async (event: any, context: Context) => {
  // https://medium.com/safara-engineering/wiring-up-typeorm-with-serverless-5cc29a18824f
  context.callbackWaitsForEmptyEventLoop = false;
  cachedServer = await bootstrapServer();
  return proxy(cachedServer, event, context, 'PROMISE').promise;
};

bootstrap.ts

import { NestFactory } from '@nestjs/core';
import { AppModule } from './app/app.module';
import { NestExpressApplication } from '@nestjs/platform-express';
import helmet from 'helmet';

export async function bootstrap(): Promise<NestExpressApplication> {
  const app = await NestFactory.create<NestExpressApplication>(AppModule);
  return app;
}

export async function useGlobal(
  app: NestExpressApplication,
): Promise<NestExpressApplication> {
  app.use(helmet());
  app.disable('x-powered-by');
  return app;
}

EDIT: I added a helper to an npm package I鈥檓 working on. Rough around the edges, but at least I have it written down somewhere 馃槃

I took a crack at it tonight, but wasn't able to get it to work yet. Here's where I'm at so far. Anyone else making progress?

```(typescript)
import { NestFactory } from '@nestjs/core';
import { ExpressAdapter } from '@nestjs/platform-express';
import serverlessExpress from '@vendia/serverless-express';
import { Context } from 'aws-lambda';
import express from 'express';
import { RequestListener } from 'http';

import { AppModule } from './app.module';

let cachedApp: RequestListener;

async function bootstrap() {
if (!cachedApp) {
const expressApp = express();
const nestApp = await NestFactory.create(
AppModule,
new ExpressAdapter(expressApp),
);

nestApp.enableCors();

await nestApp.init();

cachedApp = expressApp;

}

return cachedApp;
}

export const handler = async (event: any, context: Context, callback: any) => {
const app = await bootstrap();
const { handler } = serverlessExpress({ app });

handler(event, context, callback);
};
```

Here's the repo if you are interested in trying it out (yarn sls:offline to test).
https://github.com/michaelmerrill/nestjs-vendia

@michaelmerrill here鈥檚 a working example with sls-offline 馃槃

nest-with-sls.zip

I did use my helper package, but it鈥檚 essentially similar code to what I posted above. I still use nest鈥檚 start:dev to write and debug my code, and I consider sls-offline similar to start:prod.

Here鈥檚 the serverless.yml

service: nestjs-with-sls

plugins:
  - serverless-offline

provider:
  name: aws
  runtime: nodejs12.x
  lambdaHashingVersion: 20201221
  apiGateway:
    shouldStartNameWithService: true

functions:
  main: # The name of the lambda function
    # The module 'handler' is exported in the file 'src/lambda'
    handler: dist/lambda.handler
    events:
      - http:
          method: any
          path: /{any+}

And package.json scripts:

{
    "presls:offline": "npm run build",
    "sls:offline": "sls offline",
}

The rest follows the instructions here

thanks @jlarmstrongiv! Awesome work! Have you found a way to get this working with @vendia/serverless-express?

@michaelmerrill I wish @vendia/serverless-express was a drop in replacement for aws-serverless-express. Last I checked, I received either an error or incorrect typescript typings. So I thought it would be easier just to wait and switch over later once the moving dust settles 馃槀 It鈥檚 on the todo list

@vendia/serverless-express v3 should be a drop-in replacement. v4 is the breaking change. Is there a way to write a test that verifies Typescript typings?

@brettstack any advice on if we should be using proxy or handler for this case?

Alright, I think I ended up figuring it out. I've updated my repo. If I'm off or missing something let me know!

Nice! I got it working with npm i && sls deploy. Do you want to send a PR adding this to the examples directory here? A few things to update before that:

  1. In serverless.yml
service: nestjs-vendia
...
provider:
 name: aws
 runtime: nodejs14.x

functions:
 main: # The name of the lambda function
    # The module 'handler' is exported in the file 'src/lambda'
    handler: src/lambda.handler
    events:
      - http:
         method: ANY
         path: /
      - http: 
         method: ANY
         path: '{proxy+}'
  1. Update instructions for deploying. Maybe add a "deploy": "sls deploy" script, npm i -D serverless, and update README?

@michaelmerrill Thank you! That works like a charm! :+1:

@michaelmerrill did you want to send a PR for this or should I go ahead and add it and attribute?

I tried to create a pr, but husky is requiring tests to pass on the develop branch that are failing.

Looks like the integration test for the basic-starter example:

 FAIL  __tests__/integration.js
    console.log src/index.js:123
    ERROR: aws-serverless-express error
    console.error src/index.js:124
      TypeError: Cannot read property 'headers' of null
      ...

Sorry! Instructions are outdated. Please submit against the mainline branch

PR created. Please let me know if it needs updating.

As a side note, I find it odd that when using sls-offline I'm unable to hit a / route. The only way to get a successful response is by setting a path (ie /hello).

For example, if I create a new Nestjs application and start up the server locally I can visit localhost:3000 and get a 200. If I take that same application and access it via serverless with a path such as

functions:
  main:
    handler: src/lambda.handler
    events:
      - http:
         method: ANY
         path: /

Visiting localhost:3000/dev return a cannot GET null. I'm not sure if this is a serverless issue, nestjs issue, or something else.

I'll need to do more sls-offlinen testing

Thanks again! This is a great addition. I've seen a lot of people asking about NestJS

:tada: This issue has been resolved in version 4.2.0 :tada:

The release is available on:

Your semantic-release bot :package::rocket:

Hi, I use this snippet and i want support websocket gateway.
I already configure my serverless.yml. when i request my socket endpoint I receive the data of my request in the variable event, but it does not forward it to my gateway.
How resolve this?

my handler

```js import { ValidationPipe } from '@nestjs/common';
import { NestFactory } from '@nestjs/core';
import { ExpressAdapter } from '@nestjs/platform-express';
import { APIGatewayProxyHandler } from 'aws-lambda';
import { createServer, proxy } from 'aws-serverless-express';
import { eventContext } from 'aws-serverless-express/middleware';
import express from 'express';
import { Server } from 'http';
import { AppModule } from './app.module';

let cachedServer: Server;

const bootstrapServer = async (): Promise => {
const expressApp = express();
expressApp.use(eventContext());
const app = await NestFactory.create(
AppModule,
new ExpressAdapter(expressApp),
);
app.useGlobalPipes(new ValidationPipe({ forbidUnknownValues: true }));
app.enableCors();
await app.init();
return createServer(expressApp);
};

export const handler: APIGatewayProxyHandler = async (event, context) => {
if (!cachedServer) {
cachedServer = await bootstrapServer();
}
console.log('---event', event);
// console.log('---context', context);

return proxy(cachedServer, event, context, 'PROMISE').promise;
};

Was this page helpful?
0 / 5 - 0 ratings

Related issues

denneulin picture denneulin  路  8Comments

donny08 picture donny08  路  8Comments

dougmoscrop picture dougmoscrop  路  5Comments

bansalrachita picture bansalrachita  路  4Comments

Muthuveerappanv picture Muthuveerappanv  路  6Comments