Highly confusing express.Router() issue

Hey,

I have a large application build with NestJS that I deploy using the serverless framework. I have been doing this for some time and everything has been great. A couple of days ago I had to update to Nestjs 7 and I have been experiencing a lot of issues bootstrapping my application when it is deployed to aws. After going through countless frustrating attempts to resolve the issue it appears it’s actually nothing to do with the Nestjs/serverless bootstrapping process at all and apollo-server-express was unable to access the express router - failing with the error:

express_1.default.Router is not a function

Finally I realised that when I import express directly and try and access express.Router() I have the same issue. So I made a very simple test:

lambda.ts:

import { Context, Handler } from "aws-lambda";
import express from "express";

export const handler: Handler = async (event: any, context: Context) => {
  console.log("Import express:", express);
  console.log("Test express app: ", express());
  console.log("Test router:", express.Router());

  /* express.Router() ->
  ERROR TypeError: express_1.default.Router is not a function at 
  /var/task/dist/lambda.js:19:51 at Generator.next (<anonymous>) at 
  /var/task/dist/lambda.js:8:71 at new Promise (<anonymous>) at 
  __awaiter (/var/task/dist/lambda.js:4:12) at exports.handler (/var/task/dist/lambda.js:16:39) at 
  Runtime.handler (/var/task/serverless_sdk/index.js:9:131872) at 
  Runtime.handleOnce (/var/runtime/Runtime.js:66:25)
  */
};

This fails with the error in the comment as previously stated. Here are the other files:

serverless.yml:

service: xxxxx
app: xxxx
tenant: xxxxx

plugins:
  - serverless-pseudo-parameters
  - serverless-prune-plugin
  - serverless-deployment-bucket

provider:
  name: aws
  runtime: nodejs12.x
  region: eu-west-1
  stage: dev
  timeout: 29
  memorySize: 3008
  deploymentBucket:
    name: ${self:service}-${self:custom.currentStage}-deployment-bucket
    serverSideEncryption: AES256

custom: ${file(./serverless-common.yml):custom}
package:
  include:
    - ./dist/**
  exclude:
    - node_modules/aws-sdk/**

functions:
  index:
    handler: ./dist/lambda.handler
    name: bm-${self:custom.currentStage}-express-test
    events:
      - http:
          path: "/{proxy+}"
          method: POST

package.json:

{
  "name": "@xxx/XXXXXX",
  "version": "0.1.13",
  "dependencies": {
    "express": "4.17.1"
  },
  "devDependencies": {
    "serverless-deployment-bucket": "1.1.1",
    "serverless-prune-plugin": "1.4.2",
    "serverless-pseudo-parameters": "2.5.0",
    "ts-node": "^8.7.0",
    "tsconfig-paths": "^3.7.0",
    "tslint": "5.12.1",
    "tslint-config-prettier": "^1.18.0",
    "typescript": "^3.8.3"
  }
}

tsconfig.json:

{
  "compilerOptions": {
    "baseUrl": "./",
    "paths": {
      "@root/*": ["src/*"]
    },
    "module": "commonjs",
    "moduleResolution": "node",
    "declaration": true,
    "removeComments": true,
    "emitDecoratorMetadata": true,
    "experimentalDecorators": true,
    "target": "es6",
    "sourceMap": true,
    "outDir": "dist",
    "esModuleInterop": true
  },
  "include": ["*"],
  "exclude": ["**/node_modules/**/*", "dist"]
}

I have been working on this problem for ages before narrowing it down to this. If anybody is able to shed any light on the above that would be greatly appreciated as it’s obviously blocking me.

I would like to highlight that this code only fails once deployed to lambda. It runs fine locally which would make indicate that perhaps something was up with the packaging process but the zip file contains the correct code and dependencies.

Many thanks,
Mark

OK it appears that if I:

import Router from 'express/lib/router'

then I get a router instance. This is the same instance the express index should export.

So I am close but this feels wrong, I haven’t changed anything, I feel like I have some kind of incorrect module configuration or something.

So why can’t I do express.Router() .Any ideas would be greatly appreciated.

OK so an update. In the end I patched apollo-server-express so that it gets the router instance from lib/router and then everything works as expected.

I obviously do not want to do this so I really need to work out what’s causing this.

I have exactly the same issue using express directly.
I haven’t changed anything from previous release and i’m getting this error.
@TreeMan360 thanks for sharing the fix, it works.

I’m looking forward for official response from serverless team.

No sorry, seems the fix doesn’t works for me, the response body contains also the header.
This issue doesn’t make any sense for me.

Hey,

I AM SO GLAD SOMEONE ELSE HAS THIS PROBLEM!

This has been driving me nuts this week. Here is a gist of my patched ApolloServer.js:

Key points:

Where express_1 was defined previously I added an additional require for the direct import:

const express_1 = __importDefault(require("express"));
const express_router_1 = require("express/lib/router");

Then I changed it to use this to access the Router instance and console log so I can verify my change is still present.

getMiddleware({
    path,
    cors,
    bodyParserConfig,
    disableHealthCheck,
    onHealthCheck
  } = {}) {
    if (!path) path = "/graphql";

    console.log(
      "MARK apolloServerExpresss. ApolloServer.applyMiddleware PATCH"
    );
    const router = express_router_1();
    console.log("ROUTER:", router);

    // const router = express_1.default.Router();
    const promiseWillStart = this.willStart();

I would make a proper patch module for this but I want to find out what’s going on here. I must have some kind of module configuration wrong but I just can’t think what.

I hope the above at least gets you unblocked temporarily. If you do resolve this can you please let me know, I will do the same.

Thanks,
Mark

FWIW this is a stack overflow question I am going to put up for bounty:

Ahhh I only just noticed this.

I am sitting here trying to work this out also and I thought it was an issue with serverless-http but based on your comment it must be related to the express problem.

You are right this issue makes zero sense to me either and if this is the same root cause then it’s now an even bigger problem for me :frowning:

@mattia are you aware what triggered the issue to start happening with you? Are you just running express on lambda (without nestjs etc…)?

I am TreeMan360 on discord if you want to chat.

ok @TreeMan360, i’m aware what trigger the issue, very strange bug has very strange solution.
Try lo disable Serverless Framework Enterprise (if it’s enabled), you can just comment the tenant and app rows into your serverless.yml file, and deploy the app again.
I think that there’s a bug in the last version of the serverless-sdk.

Let me know.

Good deduction, that seems feasible, I didn’t even think of that. This indeed fixes the problem in my test lambda. Legend :slight_smile:

Untangling myself from enterprise is a pain as I use the secrets manager for stage environment variables.

I have sent a message on the dashboard chat. Let’s hope we can get this resolved ASAP. Definitely with a regression test :wink:

@TreeMan360 let me know when you will get an answer from serverless support. :wink:

1 Like

Hey Guys,

I ran into the same issue and was going mad myself too. I went back to a very basic example from the serverless site and rebuild my application from there. At the end I noticed that the sample does not have the serverless account declaration at the beginning of the serverless.yaml file. So I went back to my original code, commented out the variables and hey-presto, it worked.

service: *******
# Serverless platform settings
#app: *******
#org: *******

Not the cleanest option and I’d love to have a permanent solution. Just wanted to share my experience.

Any update from support yet?

Steit!