Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

FastifyAdapter can't take a FastifyInstance due to a type error #12979

Closed
3 of 15 tasks
toptal-dave opened this issue Dec 30, 2023 · 6 comments
Closed
3 of 15 tasks

FastifyAdapter can't take a FastifyInstance due to a type error #12979

toptal-dave opened this issue Dec 30, 2023 · 6 comments
Labels
needs triage This issue has not been looked into

Comments

@toptal-dave
Copy link

Is there an existing issue for this?

  • I have searched the existing issues

Current behavior

I am following some example code (that needed adapting) to deploy a Nest.js application using Fastify on AWS Lambda. The code is found in this issue and this PR.

When I adapt the code to be error free, I get a type error at the point where I create the FastifyAdapter using a FastifyInstance:

Argument of type 'FastifyInstance<RawServerDefault, IncomingMessage, ServerResponse<IncomingMessage>, FastifyBaseLogger, FastifyTypeProviderDefault>' is not assignable to parameter of type 'FastifyHttp2Options<any, FastifyBaseLogger> | FastifyHttp2SecureOptions<any, FastifyBaseLogger> | FastifyHttpsOptions<...> | FastifyInstance<...> | FastifyServerOptions<...>'.
  Type 'FastifyInstance<RawServerDefault, IncomingMessage, ServerResponse<IncomingMessage>, FastifyBaseLogger, FastifyTypeProviderDefault>' is not assignable to type 'FastifyInstance<any, any, any, FastifyBaseLogger, FastifyTypeProviderDefault>'.
    The types of 'withTypeProvider().decorate' are incompatible between these types.
      Type 'DecorationMethod<FastifyInstance<RawServerDefault, IncomingMessage, ServerResponse<IncomingMessage>, FastifyBaseLogger, Provider>, FastifyInstance<...>>' is not assignable to type 'DecorationMethod<FastifyInstance<any, any, any, FastifyBaseLogger, Provider>, FastifyInstance<any, any, any, FastifyBaseLogger, Provider>>'.
        Target signature provides too few arguments. Expected 2 or more, but got 1.

Fastify type error

Minimum reproduction code

https://github.com/toptal-dave/fastify-adapter-type-error

Steps to reproduce

All I did was replace the original code with the following code in the main.ts:

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 fastify, { FastifyInstance, FastifyServerOptions } from 'fastify';
import { Context, APIGatewayProxyEvent } from 'aws-lambda';
import { Logger } from '@nestjs/common';

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

let cachedNestApp;

async function bootstrap(): 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<PromiseHandler> => {
  if (!cachedNestApp) {
    const nestApp: NestApp = await bootstrap();
    cachedNestApp = awsLambdaFastify(nestApp.instance, {
      decorateRequest: true,
    });
  }

  return cachedNestApp(event, context);
};

Expected behavior

It should be possible to pass a FastifyInstance into FastifyAdapter without causing a type error because it looks like FastifyAdapter takes either options or an instance by the typing:

FastifyAdapter argument types

Package

  • I don't know. Or some 3rd-party package
  • @nestjs/common
  • @nestjs/core
  • @nestjs/microservices
  • @nestjs/platform-express
  • @nestjs/platform-fastify
  • @nestjs/platform-socket.io
  • @nestjs/platform-ws
  • @nestjs/testing
  • @nestjs/websockets
  • Other (see below)

Other package

@fastify/aws-lambda

NestJS version

10.3.0

Packages versions

{
  "name": "@iluvcoffee/application",
  "version": "0.0.1",
  "description": "",
  "author": "",
  "private": true,
  "license": "UNLICENSED",
  "scripts": {
    "build": "nest build",
    "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
    "start": "nest start",
    "start:dev": "nest start --watch",
    "start:debug": "nest start --debug --watch",
    "start:prod": "node dist/main",
    "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
    "test": "jest",
    "test:watch": "jest --watch",
    "test:cov": "jest --coverage",
    "test:watch:cov": "jest --watch --coverage",
    "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
    "test:e2e": "jest --config ./test/jest-e2e.json"
  },
  "dependencies": {
    "@fastify/aws-lambda": "^3.5.0",
    "@nestjs/common": "^10.0.0",
    "@nestjs/core": "^10.0.0",
    "@nestjs/mapped-types": "^2.0.4",
    "@nestjs/platform-express": "^10.0.0",
    "@nestjs/platform-fastify": "^10.3.0",
    "@nestjs/typeorm": "^10.0.1",
    "aws-lambda": "^1.0.7",
    "class-transformer": "^0.5.1",
    "class-validator": "^0.14.0",
    "fastify": "^4.25.2",
    "fastify-cors": "^6.1.0",
    "pg": "^8.11.3",
    "reflect-metadata": "^0.1.13",
    "rxjs": "^7.8.1",
    "typeorm": "^0.3.17"
  },
  "devDependencies": {
    "@nestjs/cli": "^10.0.0",
    "@nestjs/schematics": "^10.0.0",
    "@nestjs/testing": "^10.0.0",
    "@types/express": "^4.17.17",
    "@types/jest": "^29.5.2",
    "@types/node": "^20.3.1",
    "@types/supertest": "^2.0.12",
    "@typescript-eslint/eslint-plugin": "^6.0.0",
    "@typescript-eslint/parser": "^6.0.0",
    "eslint": "^8.42.0",
    "eslint-config-prettier": "^9.0.0",
    "eslint-plugin-prettier": "^5.0.0",
    "jest": "^29.5.0",
    "prettier": "^3.0.0",
    "source-map-support": "^0.5.21",
    "supertest": "^6.3.3",
    "ts-jest": "^29.1.0",
    "ts-loader": "^9.4.3",
    "ts-node": "^10.9.1",
    "tsconfig-paths": "^4.2.0",
    "typescript": "^5.1.3"
  },
  "jest": {
    "moduleFileExtensions": [
      "js",
      "json",
      "ts"
    ],
    "rootDir": "src",
    "testRegex": ".*\\.spec\\.ts$",
    "transform": {
      "^.+\\.(t|j)s$": "ts-jest"
    },
    "collectCoverageFrom": [
      "**/*.(t|j)s"
    ],
    "coverageDirectory": "../coverage",
    "testEnvironment": "node"
  }
}

Also, the output of npx nest info:


 _   _             _      ___  _____  _____  _     _____
| \ | |           | |    |_  |/  ___|/  __ \| |   |_   _|
|  \| |  ___  ___ | |_     | |\ `--. | /  \/| |     | |
| . ` | / _ \/ __|| __|    | | `--. \| |    | |     | |
| |\  ||  __/\__ \| |_ /\__/ //\__/ /| \__/\| |_____| |_
\_| \_/ \___||___/ \__|\____/ \____/  \____/\_____/\___/


[System Information]
OS Version     : Linux 6.6
NodeJS Version : v18.19.0
NPM Version    : 10.2.3 

[Nest CLI]
Nest CLI Version : 10.2.1 

[Nest Platform Information]
platform-express version : 10.3.0
platform-fastify version : 10.3.0
mapped-types version     : 2.0.4
schematics version       : 10.0.3
typeorm version          : 10.0.1
testing version          : 10.3.0
common version           : 10.3.0
core version             : 10.3.0
cli version              : 10.2.1

Node.js version

18.19.0

In which operating systems have you tested?

  • macOS
  • Windows
  • Linux

Other

No response

@toptal-dave toptal-dave added the needs triage This issue has not been looked into label Dec 30, 2023
@toptal-dave
Copy link
Author

Do you think a better approach would be to use the getHttpAdapter or getHttpServer or similar from the returned promise to get the Fastify server?

@toptal-dave
Copy link
Author

I was able to fix the code myself using the nestApp.getHttpAdapter().getHttpServer() to get the instance:

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 know it's a separate issue but it would be pretty cool to get the serverless documentation updated with a proper example of how to run a Fastify-based Nest.js application in a Lambda. I'm trying this specific solution to reach a balance of speed (which lowers cost in Lambdas) & avoiding running an instance anywhere (as a container or an EC2) to demonstrate and MVP for a project with Nest.js. It's pretty cool to think that you could start a project this way and when traffic increases, with a few small tweaks, you could probably migrate to EKS or Kubernetes.

@toptal-dave
Copy link
Author

Actually I don't think that fixes it entirely because when I try to run the code as a containerized Lambda created by SST, I get the following error:

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)"
    ]
}

There's a demo repo here. Perhaps using getHttpAdapter().getHttpServer() isn't the way to get an instance after all...

@kamilmysliwiec
Copy link
Member

Your local fastify dependency version must match the one installed by the @nestjs/platform-fastify package

@toptal-dave
Copy link
Author

toptal-dave commented Jan 5, 2024

@kamilmysliwiec thanks for the response! I'm going through the fundamentals course now so it feels like I'm replying to a celebrity!

Anyway, I took your advice and locked the version to 4.25.1; however, I'm still getting the decorate response error:

node_modules/@nestjs/platform-fastify/package.json

node_modules/@nestjs/platform-fastify/node_modules/fastify

packages/application/package.json

packages/application/node_modules/fastify/package.json

The error is as follows:

2024-01-05T11:53:49.911Z	3c1d2db4-a768-4e91-ba05-44c73b9982e2	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)"
    ]
}

I think my next step is actually going to be to abandon the serverless Fastify adapter and see if I can get it working without. I was able to recently deploy to AWS via an ECS+EC2+ASG+ALB setup but for demo-ing and early growth, Lambda would be extremely awesome.

@toptal-dave
Copy link
Author

Actually, @kamilmysliwiec , when I restored the code to a prior version that declared the instance separately instead of trying to reference it (here), I was able to get it working. Thanks for the help!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
needs triage This issue has not been looked into
Projects
None yet
Development

No branches or pull requests

2 participants