aws-lambda-fastify
aws-lambda-fastify copied to clipboard
Notes on Decorating Request when reusing Fastify instance
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
Not really a bug, but just might be worth adding a note in the readme.
The new feature to decorate the request with event and context data introduces errors if you are caching the fastify instance in order to save time bootstrapping.
For example I have a NestJS application I run on lambda, to avoid having to build the app every request it keeps it in memory until the lambda instance goes down.
let cachedNestApp: NestApp;
export const handler: Handler = async (
event: APIGatewayProxyEvent,
context: Context,
): Promise<APIGatewayProxyResult> => {
if (!cachedNestApp) {
cachedNestApp = await bootstrap();
}
const proxy = awsLambdaFastify(cachedNestApp.instance);
return proxy(event, context);
};
I upgraded to the latest version of aws-lambda-fastify
from 1.7.1 and the new decoration by default means that the first request works fine but afterwards every request errors:
{
"errorType": "FastifyError",
"errorMessage": "The decorator 'awsLambda' has been added after start!",
"code": "FST_ERR_DEC_AFTER_START",
"name": "FastifyError",
"message": "The decorator 'awsLambda' has been added after start!",
"statusCode": 500,
"stack": [
"FastifyError: The decorator 'awsLambda' has been added after start!",
" at assertNotStarted (/var/task/node_modules/fastify/lib/decorate.js:127:11)",
" at Object.decorateRequest (/var/task/node_modules/fastify/lib/decorate.js:119:3)",
" at module.exports (/var/task/node_modules/aws-lambda-fastify/index.js:9:9)",
" at Runtime.handler (/var/task/src/aws-lambda-fastify.js:54:19)",
" at Runtime.handleOnce (/var/runtime/Runtime.js:66:25)"
]
}
I think this is expected behaviour, but just might come as a surprise to some who might upgrade and run into the same issues so i thought it might be worth noting in the readme.
If you really want to cache the nest instance, you probably also need to cache the awsLambdaFastify(cachedNestApp.instance);
proxy.
I'll give it a shot, for now I just turned off the decoration as not using it.
I think you just need to cache the proxy, not the nest app.
Thanks, I experience the exact same issue with a similar app. I'll also try to disable the decorators and see if this is enough for me.
Strangely, I'm seeing issues with NestJS even with a cached proxy instance. Mine is a different error though:
2022-01-04T00:08:28.566Z ed7873b9-5086-42b7-9a14-79b0b28b6fb7 ERROR Uncaught Exception
{
"errorType": "TypeError",
"errorMessage": "Cannot read property 'event' of undefined",
"stack": [
"TypeError: Cannot read property 'event' of undefined",
" at genReqId (/var/task/dist/internal-lambda.js:8:903277)",
" at Object.X [as handler] (/var/task/dist/internal-lambda.js:8:1006807)",
" at g.lookup (/var/task/dist/internal-lambda.js:8:360800)",
" at /var/task/dist/internal-lambda.js:8:29516",
" at p.prepare (/var/task/dist/internal-lambda.js:8:33476)",
" at m (/var/task/dist/internal-lambda.js:8:29501)",
" at y (/var/task/dist/internal-lambda.js:8:29797)",
" at h (/var/task/dist/internal-lambda.js:8:29331)",
" at /var/task/dist/internal-lambda.js:8:951755",
" at i (/var/task/dist/internal-lambda.js:8:953153)"
]
}
Code looks like:
let cachedProxy: PromiseHandler
type LambdaFastifyRequest = FastifyRequest & { awsLambda: { event: APIGatewayProxyEvent; context: Context } }
...
const bootstrap = async () => {
const app = await NestFactory.create<NestFastifyApplication>(
module,
new FastifyAdapter({
logger: true,
genReqId: (request: unknown): string => {
return (request as LambdaFastifyRequest).awsLambda.event.requestContext.requestId
},
}),
{
bufferLogs: true,
},
)
...
}
...
export const handle = async (event: APIGatewayProxyEvent, context: Context): Promise<APIGatewayProxyResult> => {
if (!cachedProxy) {
cachedProxy = awsLambdaFastify(await bootstrap(), { decorateRequest: true })
}
return cachedProxy(event, context)
}
This is using 2.0.2
. It seems like NestJS isn't carrying through the decoration properly. I've had to switch back to the old "JSON serialization in headers" method for now.
Can you provide a reproducible github repo example?
Yeah I can look into doing so. It's going to take me a bit as I'm a bit swamped this week, but I'll put it on my list.
This might be a very naive approach to cache the proxy instance, but it does seem to be working. I'm not sure about the cold start side effect of using the ready()
method.
import { NestFactory } from '@nestjs/core';
import {
FastifyAdapter,
NestFastifyApplication,
} from '@nestjs/platform-fastify';
import { AppModule } from './app.module';
import 'reflect-metadata';
import { FastifyServerOptions, FastifyInstance, fastify } from 'fastify';
import {
Context,
APIGatewayProxyEvent,
APIGatewayProxyResult,
} from 'aws-lambda';
import { Logger } from '@nestjs/common';
import awsLambdaFastify, {
LambdaResponse,
PromiseHandler,
} from 'aws-lambda-fastify';
interface NestApp {
app: NestFastifyApplication;
instance: FastifyInstance;
}
let cachedNestApp: NestApp;
let cachedProxy: PromiseHandler<unknown, LambdaResponse>;
async function bootstrapServer(): Promise<NestApp> {
const serverOptions: FastifyServerOptions = { logger: true };
const instance: FastifyInstance = fastify(serverOptions);
const app = await NestFactory.create<NestFastifyApplication>(
AppModule,
new FastifyAdapter(instance),
{ logger: !process.env.AWS_EXECUTION_ENV ? new Logger() : console },
);
app.setGlobalPrefix(process.env.API_PREFIX);
await app.init();
return { app, instance };
}
export const handler = async (
event: APIGatewayProxyEvent,
context: Context,
): Promise<APIGatewayProxyResult> => {
if (!cachedNestApp) {
cachedNestApp = await bootstrapServer();
}
if (!cachedProxy) {
cachedProxy = awsLambdaFastify(cachedNestApp.instance, {
decorateRequest: true,
});
await cachedNestApp.instance.ready();
}
return cachedProxy(event, context);
};
I would recommend to use ESM and move the two await to be top-level awaits and just use the proxy as your handler.