nestjs / nest

A progressive Node.js framework for building efficient, scalable, and enterprise-grade server-side applications with TypeScript/JavaScript 🚀
https://nestjs.com
MIT License
66.89k stars 7.56k forks source link

Deploying to google cloud functions #238

Closed ArsalaBangash closed 6 years ago

ArsalaBangash commented 6 years ago

I'm building the backend for a startup. We were initially going to deploy on the google app engine but now the team has decided to switch over to cloud functions. I've already built a great deal of the backend using the nestjs framework and I love it. Is there any way I could use the nest framework in conjunction with google cloud functions?

jgordor commented 6 years ago

I dream with a @Controller that additionally to bind to a local express server, can be configured to proxy the incoming request from API Gateway->Lamdba/functions, transforming the request data to the controller function (server/platform agnostic).

I think that for AWS lambda, using this project https://github.com/awslabs/aws-serverless-express as a base, it can be done

kamilmysliwiec commented 6 years ago

Hi @ArsalaBangash, Has https://github.com/awslabs/aws-serverless-express helped you? 🙂

kamilmysliwiec commented 6 years ago

Hi @ArsalaBangash, Let us know whenever you have some issues 🏷

popofr13 commented 6 years ago

Hi @kamilmysliwiec,

We are finalizing our first project with Typescript / Nodejs stack that will run on AWS Lambda. We used the serverless framework, Sequelize for access to SQL databases, and Inversify as DI container.

I recently discovered NestJS, and this framework look very interesting to better structure our Typescript / Nodejs projects (currently PHP / Symfony / DDD). However, I did not manage to use it with AWS Lambda :( I tried the "aws-serverless-express" and "serverless-http" packages, but I could not find anything that worked.

It might be interesting to have a small recipe / section on the website to help newbie as me ^^

Serverless is a very trending word! ;)

Thanks

Vincent-Pang commented 6 years ago

I have tried the "aws-serverless-express" with nestjs, and it works from time to time. It works at the first request, and then the subsequent requests do not work (aws lambda timeout). And then a while later, it works again for a request.

danitt commented 6 years ago

@popofr13 @Vincent-Pang @jgordor this is probably not the best way, but at least as a POC it deploys successfully to lambda

(uses serverless-plugin-typescript and serverless-offline plugins)

handler.ts

import * as serverless from 'aws-serverless-express';
import { NestFactory } from '@nestjs/core';
import { ApplicationModule } from './src/app.module';
const express = require('express')();

module.exports.nest = async (event, context) => {
    const app = await NestFactory.create(ApplicationModule, express);
    await app.init();
    const server = serverless.createServer(express);
    serverless.proxy(server, event, context);
};
chanlito commented 6 years ago

@danitt I'm wondering how good it is using nest with aws lambda? Did you run to any problems?

danitt commented 6 years ago

@chanlito literally just got basic deployment working a few minutes ago, but hoping to test it out on a side project this month - I have big hopes for it, Angular-style architecture in the backend sounds amazing

Vincent-Pang commented 6 years ago

@danitt Great, my code is similar to yours, and I found that it works after upgrading the nest to the latest version. Thank you for your POC.

btw, anyone has ideas to make the log in aws cloudwatch more pretty? Current log sample ...

START RequestId: a5ec9ddf-21d8-11e8-8970-2dc14502f742 Version: $LATEST
\u001b[32m[Nest] 1 - \u001b[39m3/7/2018, 7:25:01 AM \u001b[38;5;3m[NestFactory] \u001b[39m\u001b[32mStarting Nest application...\u001b[39m\u001b[38;5;3m +704ms\u001b[39m
\u001b[32m[Nest] 1 - \u001b[39m3/7/2018, 7:25:01 AM \u001b[38;5;3m[InstanceLoader] \u001b[39m\u001b[32mApplicationModule dependencies initialized\u001b[39m\u001b[38;5;3m +3ms\u001b[39m
...
danitt commented 6 years ago

@Vincent-Pang can you show us how you implemented it, I'm curious

as for logging, could take inspiration from this: https://github.com/rotemtam/serverless-aws-logs-parser

besasch88 commented 6 years ago

Sorry...i'm trying to deploy nodeJS used as graphQL-api gateway to a lambda function...Did someone try to deploy in a lambda? Is there a little guide on it? Thanks!!!

jonnyasmar commented 6 years ago

@danitt I've managed to use your solution to get things working in serverless-offline and it deploys just fine, but I'm getting 502 bad gateway when trying to access to the deployed route. Did you encounter this at all in your development? My code looks like this:

Nested.js

import { Callback, Context, Handler } from 'aws-lambda';

import { NestFactory } from '@nestjs/core';
import { Module } from '@nestjs/common';
import * as serverless from 'aws-serverless-express';
const express = require('express')();

import { Get, Controller, Req } from '@nestjs/common';

@Controller('nested/messages')
class AppController {
  @Get()
  root(@Req() req) {
    return {
      text: 'Hello World!'
    };
  }
}

// Was testing here to see if the issue may have been related to the `dev` prefix that is put on the route when deployed to Lambda
@Controller('dev/nested/messages')
class AppController2 {
  @Get()
  root(@Req() req) {
    return {
      text: 'Hello World!!!!'
    }
  }
}

@Module({
  imports: [],
  controllers: [AppController, AppController2],
  components: [],
})
class AppModule {}

export const handler: Handler = async (event: any, context: Context, callback: Callback) => {
  const app = await NestFactory.create(AppModule, express);
  await app.init();
  const server = serverless.createServer(express);
  serverless.proxy(server, event, context);
};

serverless.yml

service: service

plugins:
  - serverless-plugin-typescript
  - serverless-offline
  - serverless-plugin-include-dependencies

provider:
  name: aws
  runtime: nodejs8.10

custom:
  services: src/services
  Nested: ${self:custom.services}/Nested

package:
  individually: true
  include:
    ${self:custom.Nested}.ts

functions:
  nested:
    handler: ${self:custom.Nested}.handler
    events:
      - http:
          cors: true
          path: nested/{proxy+}
          method: any
jonnyasmar commented 6 years ago

@danitt (and anyone else running into this issue) Seems I've figured it out!

Turns out it's an async incompatibility with aws-serverless-express & Node 8.10. For more on this issue, see https://github.com/awslabs/aws-serverless-express/issues/134

I resolved this by falling back to traditional promise behavior instead of my precious async/await:

export const handler: Handler = (event: any, context: Context, callback: Callback) => {
  NestFactory.create(AppModule, express).then(app => {
    app.init().then(() => {
      const server = serverless.createServer(express);
      return serverless.proxy(server, event, context);
    });
  });
};

I'm still planning on finding a way to move the NestFactory.create outside of the handler, but I'm not sure it will be worth much. If anyone has any ideas or insight here, I'd love to hear it.

djeley commented 6 years ago

@jonnyasmar The following is one way to move the NestFactory.createoutside of the handler:

import { Context, Handler } from 'aws-lambda';
import { NestFactory } from '@nestjs/core';
import { ApplicationModule } from './app.module';
import { Server } from 'http';
import * as serverless from 'aws-serverless-express';

const express = require('express')();
let cachedServer: Server;

function bootstrapServer(): Promise<Server> {
  return NestFactory.create(ApplicationModule, express)
    .then(app => app.init())
    .then(() => serverless.createServer(express));
}

export const handler: Handler = (event: any, context: Context) => {
  if (!cachedServer) {
    console.log('Bootstraping server');
    bootstrapServer().then(server => {
      cachedServer = server;
      return serverless.proxy(server, event, context);
    });
  } else {
    console.log('Using cached server');
    return serverless.proxy(cachedServer, event, context);
  }
};

It appears to make response times quicker for subsequent requests, but I've not tested this in any great detail.

jonnyasmar commented 6 years ago

Thanks for the reply @djeley ! This is almost exactly how I ended up handling it :D

let served;
const createServer = () => {
  return new Promise(resolve => {
    NestFactory.create(AppModule, express).then(app => {
      app.init().then(() => {
        resolve(serverless.createServer(express));
      });
    });
  });
};

export const handler: Handler = (event: any, context: Context) => {
  if (served) return serverless.proxy(server, event, context);
  else
    createServer().then(server => {
      served = server;
      return serverless.proxy(served, event, context);
    });
};
jonnyasmar commented 6 years ago

@djeley Really like that shorthand for your promise chaining btw. Totally stealing that :smiling_imp:

kamilmysliwiec commented 6 years ago

Reopening this issue. I think we should provide a guide in the documentation 🙂

szkumorowski commented 6 years ago

What are the real benefits from deploy the express app as lambda ?

danitt commented 6 years ago

@szkumorowski scalability, huge cost savings (with exceptions), no server-side config/maintenance, enforces statelessness, to name a few

brettstack commented 6 years ago

We're working on the async support in aws-serverless-express. For now you can patch it yourself; see https://github.com/awslabs/aws-serverless-express/issues/134#issuecomment-379417431

kamilmysliwiec commented 6 years ago

Thanks @brettstack 🙂

samdelagarza commented 6 years ago

@kamilmysliwiec - looking forward to your apex documentation on this! very exciting

kamilmysliwiec commented 6 years ago

Let's track this issue in the docs repo: https://github.com/nestjs/docs.nestjs.com/issues/96

rynop commented 6 years ago

For those of you looking to deploy to APIGateway (proxy+) + Lambda, I just figured it out (Thanks @brettstack for all your work). Took me some time to piece all the parts together, hope this helps someone else out.

lambda.ts:

require('source-map-support').install();

import { Context, Handler } from 'aws-lambda';
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { Server } from 'http';
import { createServer, proxy } from 'aws-serverless-express';
import { eventContext } from 'aws-serverless-express/middleware';
import express from 'express';

let cachedServer: Server;
const expressApp = 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
const binaryMimeTypes: string[] = [];

async function bootstrapServer(): Promise<Server> {
  const nestApp = await NestFactory.create(AppModule, expressApp);
  nestApp.use(eventContext());
  await nestApp.init();
  return createServer(expressApp, undefined, binaryMimeTypes);
}

export const handler: Handler = async (event: any, context: Context) => {
  if (!cachedServer) {
    console.log('Bootstraping server');
    cachedServer = await bootstrapServer();
  } else {
    console.log('Using cached server');
  }
  return proxy(cachedServer, event, context, 'PROMISE').promise;
};

For extra credit, you can add the following to your package.json's scripts to reduce the size of your zip file prior to deploy. If someone knows of an npm module to only include the code in node_modules that typescript needs I'd appreciate it:

"package": "rm /tmp/lambda.zip; zip -r /tmp/lambda.zip dist node_modules -x 'node_modules/typescript/*' 'node_modules/@types/*'"
picosam commented 5 years ago

Thank you all. I'm currently looking for a way to deploy Apollo Server to lambda as well (https://www.apollographql.com/docs/apollo-server/v2/deployment/lambda.html) -- any tips?

martijnvdbrug commented 5 years ago

@picosam A little off topic, but this might help you https://medium.com/mistergreen-engineering/apollo-server-in-google-cloud-functions-using-nestjs-a5a64486bc8a It's using Google Cloud functions, but shouldn't matter too much

lock[bot] commented 4 years ago

This thread has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs.