aws-lambda-fastify icon indicating copy to clipboard operation
aws-lambda-fastify copied to clipboard

Use with NestJS

Open rbgfloripa opened this issue 2 years ago • 20 comments

Prerequisites

  • [X] I have written a descriptive issue title
  • [X] I have searched existing issues to ensure the issue has not already been raised

Issue

Hi all,

Does anyone already use it in production with NestJS? Is it possible to send me an example?

I searched and didn't find any information about this. The only article I found (on Medium) uses old versions.

TIA,

Rafael.

rbgfloripa avatar Jul 01 '22 21:07 rbgfloripa

maybe @cefamax can help?

btw: if an old version of aws-lambda-fastify worked, the newest version should still work

adrai avatar Jul 01 '22 21:07 adrai

Hi,

Yes, old version works, but with old versions of nest. The example code no longer works. So I wanted some help to test it with current versions (nest 8+).

Thanks

Rafael.

rbgfloripa avatar Jul 04 '22 12:07 rbgfloripa

Hi,

Yes, old version works, but with old versions of nest. The example code no longer works. So I wanted some help to test it with current versions (nest 8+).

Thanks

Rafael.

Sorry, I'm not a nest user, you may try to ask at the nest community or stackoverflow.

adrai avatar Jul 04 '22 12:07 adrai

Yeah... I already tried :-( I didn't find it anywhere But thanks man. I will keep trying to make it work.

rbgfloripa avatar Jul 04 '22 13:07 rbgfloripa

hi @rbgfloripa, I write you the lambda.ts file contents. I use in production "aws-lambda-fastify": "^2.2.0" nodejs 16.x. I hope it helps you. ` import { NestFactory } from '@nestjs/core'; import { FastifyAdapter, NestFastifyApplication } from '@nestjs/platform-fastify'; import { FastifyServerOptions, FastifyInstance, fastify } from 'fastify'; import awsLambdaFastify from 'aws-lambda-fastify'; import { AppModule } from './app.module'; import { Context, APIGatewayProxyEvent, APIGatewayProxyResult } from 'aws-lambda'; import { Logger, LogLevel } from '@nestjs/common';

interface NestApp { app: NestFastifyApplication; instance: FastifyInstance; }

let cachedNestApp: NestApp; let cachedProxy;

async function bootstrapServer(): Promise<NestApp> {

const serverOptions: FastifyServerOptions = {
    logger: (process.env.LOGGER || '0') == '1',
};
const instance: FastifyInstance = fastify(serverOptions);
const app = await NestFactory.create<NestFastifyApplication>(
    AppModule,
    new FastifyAdapter(instance),
    {
        logger: !process.env.AWS_EXECUTION_ENV ? new Logger() : console,
    },
);

const CORS_OPTIONS = {
    origin: '*',
    allowedHeaders: '*',
    exposedHeaders: '*',
    credentials: false,
    methods: ['GET', 'PUT', 'OPTIONS', 'POST', 'DELETE'],
};

app.register(require('fastify-cors'), CORS_OPTIONS);

app.setGlobalPrefix(process.env.API_PREFIX);

await app.init();

return {
    app,
    instance
};

}

export const handler = async (event: APIGatewayProxyEvent, context: Context): Promise<APIGatewayProxyResult> => {

if (!cachedProxy) {

    if (!cachedNestApp) {
        cachedNestApp = await bootstrapServer();
    }
    cachedProxy = awsLambdaFastify(cachedNestApp.instance, { decorateRequest: true });
}

return cachedProxy(event, context);

}; `

cefamax avatar Jul 05 '22 07:07 cefamax

Thanks a lot man. I'll test it right now.

rbgfloripa avatar Jul 05 '22 14:07 rbgfloripa

Anyone using v3 with nestjs?

dustingraves avatar Oct 28 '22 21:10 dustingraves

@cefamax I was able to get my fastify/mercurius graphql app running locally through serverless-offline thanks to your suggestion above; however, when I deployed to AWS, I am getting an error: EROFS: read-only file system, mkdir '/var/task/src'.

I don't know where Nest is trying to write to the lambda file system but happens right after INFO Mapped {/, GET} route RouterExplorer in the logs. Any idea where this might be happening and how to fix?

Jwagner347 avatar Dec 14 '22 21:12 Jwagner347

@Jwagner347 I have no idea what it could be, with the information you wrote. Nest shouldn't write anything to that folder. You could try building a clean nestjs application to see if it works in aws and then add the other components to check if anything outside of nest can give that problem.

cefamax avatar Dec 15 '22 11:12 cefamax

https://github.com/aws-samples/aws-lambda-elixir-runtime/issues/7 https://stackoverflow.com/questions/53810516/getting-error-aws-lambda-erofs-read-only-file-system-open-var-task-assets https://stackoverflow.com/questions/49020710/lambda-enoent-no-such-file-or-directory

/var/task is where your Lambda function code is located, and in the actual Lambda environment, that filesystem is read-only. If you need to write to a file, you need to write to /tmp.

Q: What if I need scratch space on disk for my AWS Lambda function?

Each Lambda function receives 500MB of non-persistent disk space in its own /tmp directory.

https://aws.amazon.com/lambda/faqs/

Uzlopak avatar Dec 15 '22 12:12 Uzlopak

@Uzlopak I know you can only write to /tmp in lambda, my issue is that I don't know why NestJS application is trying to write in the first place. It looks like when I call app.init() I get that error.

Jwagner347 avatar Dec 15 '22 16:12 Jwagner347

@cefamax I was able to figure it out. It's a GraphQL application, and I was using autoSchemaFile, which was trying to write the schema file on initialization. I just had to change it to only run locally and then copy over the schema during my build before deploying to lambda.

Thanks for your help, the code you initially posted helped me a lot to get up and running with fastify/mercurius in lambda. One thing I don't understand though is you are caching the proxy in addition to the app. Everywhere else I see proxy is just called directly. What is the reason for this?

Jwagner347 avatar Dec 15 '22 19:12 Jwagner347

@Jwagner347

Can you improve the readme.md to persist that knowledge?

Uzlopak avatar Dec 15 '22 19:12 Uzlopak

@Jwagner347 I had read a comment by @mcollina where he recommends doing it, but now I can't find it to send it to you. However I have been using this system for several months, I have never had any problems.

cefamax avatar Dec 16 '22 07:12 cefamax

What is the reason for this?

In most of the server-less environment, handler will be called everytime for a request and anything outside may cached between calls (determined by how long the environment may spin down).

By using a variables cache, it will minimize the cold-start time of fastify since it will be construct once vs construct everytime.

climba03003 avatar Dec 16 '22 07:12 climba03003

@climba03003 but why not do it like this:

export const handler = async (
  event: APIGatewayProxyEvent,
  context: Context,
): Promise<PromiseHandler> => {
    if (!cachedNestApp) {
      const nestApp = await bootstrap();
      cachedNestApp = awsLambdaFastify(nestApp.instance, {
        decorateRequest: true,
      });
    }

  return cachedNestApp(event, context);
};

Why cache proxy separate from the bootstrapped app? I would think that if one variable was removed from lambda cache, all variables would since you say it is determined by how long the environment spin down is set for. I am not trying to be pedantic, genuinely trying to understand where I may be missing something.

Jwagner347 avatar Dec 16 '22 16:12 Jwagner347

@Jwagner347 I understand what you mean. It seems to me that the code you wrote is better than what I had written and the same result is achieved. Thank you!

cefamax avatar Dec 16 '22 18:12 cefamax

@Uzlopak I have a PR in that updates the Readme.

I combined the suggestions and information from @cefamax and @climba03003, so let me know if I should change anything there.

Jwagner347 avatar Dec 16 '22 20:12 Jwagner347

Just updating here that the latest version of Nest requires some updates to the code in this issue:

import { NestFactory } from '@nestjs/core';
import {
  FastifyAdapter,
  NestFastifyApplication,
} from '@nestjs/platform-fastify';
import { AppModule } from './app.module';
import awsLambdaFastify, { PromiseHandler } from '@fastify/aws-lambda';
import { Context, APIGatewayProxyEvent } from 'aws-lambda';
import { Logger } from '@nestjs/common';

let cachedNestApp;

async function bootstrap(): Promise<NestFastifyApplication> {
  // Create the app
  const app = await NestFactory.create<NestFastifyApplication>(
    AppModule,
    // Use an env var to set the logger to nest or console
    new FastifyAdapter({ logger: (process.env.LOGGER || '0') == '1' }),
    {
      logger: !process.env.AWS_EXECUTION_ENV ? new Logger() : console,
    },
  );

  // Enable cors
  app.enableCors({
    origin: '*',
    allowedHeaders: '*',
    exposedHeaders: '*',
    credentials: false,
    methods: ['GET', 'PUT', 'OPTIONS', 'POST', 'DELETE'],
  });

  // Set the prefix as necessary
  app.setGlobalPrefix(process.env.API_PREFIX);

  await app.init();

  return app;
}

export const handler = async (
  event: APIGatewayProxyEvent,
  context: Context,
): Promise<PromiseHandler> => {
  // If there's no cached app
  if (!cachedNestApp) {
    // Bootstrap
    const nestApp: NestFastifyApplication = await bootstrap();
    // Create an AWS Lambda Fastify cached app from the Nest app
    cachedNestApp = awsLambdaFastify(nestApp.getHttpAdapter().getHttpServer(), {
      decorateRequest: true,
    });
  }

  return cachedNestApp(event, context);
};

I do think this should be somewhere on the web but I'm not sure if it belongs in Nest or here. To be honest, if you are worried about noisiness of the documentation, this is pretty sparse compared to Nest's documentation so it wouldn't be too noisy here comparatively. At the same time, I find their serverless documentation to be a bit underwhelming.

toptal-dave avatar Dec 31 '23 22:12 toptal-dave

Looks like I'm still running into a problem:

My set up is using SST to deploy a containerized Nest.js application using this plugin. There's a demo repo here. I get the following error when I hit the Lambda URL:

2023-12-31T23:36:48.020Z	5f37cec7-815e-4f51-a59d-7a20e93c69cf	ERROR	Invoke Error 	{
    "errorType": "TypeError",
    "errorMessage": "app.decorateRequest is not a function",
    "stack": [
        "TypeError: app.decorateRequest is not a function",
        "    at module.exports (/var/task/node_modules/@fastify/aws-lambda/index.js:23:9)",
        "    at Runtime.handler (/var/task/dist/main.js:28:50)",
        "    at process.processTicksAndRejections (node:internal/process/task_queues:95:5)"
    ]
}

Perhaps using getHttpAdapter().getHttpServer() isn't the way to do it...

toptal-dave avatar Dec 31 '23 23:12 toptal-dave