Import a Nest.js app as a simple Express middleware

While I don't condone the use of Nest as a middleware itself, it is possible. Using a basic set up from a nest new express-server -p npm to create the new NestJS application, and setting up a small express server with src/server.ts I was able to get the following code working.

app.middleware.ts

import { Injectable, NestMiddleware } from '@nestjs/common';
import { NestFactory } from '@nestjs/core';
import { ExpressAdapter } from '@nestjs/platform-express';
import { AppModule } from './app.module';

const bootstrap = async (express: Express.Application) => {
  const app = await NestFactory.create(AppModule, new ExpressAdapter(express));
  await app.init();
  return app;
}

@Injectable()
export class AppMiddleware implements NestMiddleware {

  constructor(private expressInstance: Express.Application) {}

  use(req: any, res: any, next: () => void) {
    console.log('In Nest middleware');
    return bootstrap(this.expressInstance);
  }
}

app.controller.ts

import { Controller, Get } from '@nestjs/common';
import { AppService } from './app.service';

@Controller()
export class AppController {
  constructor(private readonly appService: AppService) {}

  @Get()
  getHello(): string {
    return this.appService.getHello();
  }
}

app.service.ts

import { Injectable } from '@nestjs/common';

@Injectable()
export class AppService {
  getHello(): string {
    return 'Hello World!';
  }
}

app.module.ts

import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';

@Module({
  imports: [],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}

server.ts

import * as express from 'express';

import { AppMiddleware } from './app.middleware';

const app = express();

app.use((req, res, next) => {
  const nest = new AppMiddleware(app).use(req, res, next);
  nest.then(() => {
    next();
  }).catch(err => {
    console.log(JSON.stringify(err));
    next();
  });
});

app.listen(3000, () => {
  console.log('Listening on port 3000');
});

Build command

npm run build
# mapped to nest build

Start command

node dist/server.js

Test command

▶ curl http://localhost:3000
Hello World!

Console Log

Listening on port 3000
In Nest middleware
[Nest] 24235   - 02/18/2020, 8:05:44 PM   [NestFactory] Starting Nest application...
[Nest] 24235   - 02/18/2020, 8:05:44 PM   [InstanceLoader] AppModule dependencies initialized +15ms
[Nest] 24235   - 02/18/2020, 8:05:44 PM   [RoutesResolver] AppController {/}: +3ms
[Nest] 24235   - 02/18/2020, 8:05:44 PM   [RouterExplorer] Mapped {/, GET} route +2ms
[Nest] 24235   - 02/18/2020, 8:05:44 PM   [NestApplication] Nest application successfully started +2ms

Keep in mind a few things:

1) with this approach, unless you cache your Nest server, you will build a new Nest server on each request, which will only slow your project down more as you grow with the Nest side of things.

2) You could instead pass your existing express server to the ExpressAdapter as you are partially doing in your existing code and start the server from the Nest app.listen() function instead. Just make sure to remove any error handling middleware as it will start to conflict with how Nest handles responses. You should instead move those functions to ExceptionFilters.

3) One of the errors in your app.middleware is that you are creating not only a new Nest instance on each call, but a new express instance too, which could really be confusing the node server.

4) The error that was coming in as [Object object] in case you were wondering, was a standard Express error Cannot GET /. Dunno why it was serialized strangely, but a JSON.stringify() in the catch helped resolve it.

Overall, I would not recommend this approach but it is possible to do.


@Etienne your bootstrap function is actually fine as it is and you can use it directly in express-app.ts. Advantages:

  • No new Nest instance per request
  • Independent configuration for each express app

app.middleware.ts

import {NestFactory} from '@nestjs/core';
import {AppModule} from './app.module';
import {ExpressAdapter} from '@nestjs/platform-express';
import express from 'express';

export const bootstrap = async () => {
  const expressApp = express();
  const adapter = new ExpressAdapter(expressApp);
  const app = await NestFactory.create(AppModule, adapter);
  await app.init();
  return app;
};

express-app.ts

import express from 'express';
import { bootstrap } from './app.middleware';

const app = express();
const PORT = process.env.PORT || 3000;

bootstrap().then(expressApp => {
  app.use(expressApp);

  app.listen(PORT, () => {
    console.log(`app running on port ${PORT}`);
  });
});

I know it’s not exactly the answer to the question, but I’d just like to leave an example using this middleware.

For my context, I thought it was okay to put everything in the nest, instead of putting the nest in the express. I needed to put all my standard express application, working with the node, with no special conditions, just join the 2, this was my scenario.

I just took the global settings, like body-parser and dotenv and put it in my main file.

src/main.ts

import dotenv from 'dotenv'
import bodyParser from 'body-parser'
import { useExpress } from './workspaces/poc/server'
import { TodoModule } from './workspaces/todo/todo.module'
import { NestFactory } from '@nestjs/core';

// was in src/workspaces/my-legacy-app/server.ts
dotenv.config()

async function bootstrap() {
  const app = await NestFactory.create(TodoModule);
  app.use(bodyParser.json());

  // was in src/workspaces/my-legacy-app/server.ts
  // also did not know how to resolve the issue of types, so use "any"
  useExpress(app.getHttpAdapter() as any)

  await app.listen(3000,() => {
    console.info(`App runnning on port: ${3000}`)
  });
}
bootstrap();

My old legacy main file

src/workspaces/legacy-app/server.ts

import { validatorMiddleware } from './middlewares/validator.middleware'
import { logMiddleware } from './middlewares/log.middleware'
import { userRouter } from './routes/user.route'
import { Express } from 'express'

export function useExpress(server: Express){

  server.use(validatorMiddleware)
  server.use(logMiddleware)
  server.use('/user', userRouter)
  
  // commented because the server will go up here more, but just to show that it was the same way as in express
  // server.listen(
  //   process.env.PORT,
  //   () => console.log(`Server is running on port ${process.env.PORT ?? 3000}`)
  // )
}