Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Import a Nest.js app as a simple Express middleware

I've a Nestjs app (a Rest API) that I would like to import in another node module, as a simple Express middleware (not a Nest middleware). Actually I'm still not able to make it working.

// main.ts  
// => The main file of my Nest app, this one is working properly.

import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  await app.listen(3000);
}
bootstrap();

// app.middleware.ts

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

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

@Injectable()
export class AppMiddleware implements NestMiddleware {
  use(req: Request, res: Response, next: Function) {
    return bootstrap();
  }
}
// express-app.ts  
// => Here I'm trying to load my app through a simple Express middleware, but it doesn't works.

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

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

app.use((req, res, next) => {
  const app = new AppMiddleware().use(req, res, next);
  app.then(next);
});

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

When running my app from main.ts it's working properly (all the routes are working and I'm getting the correct data). However when I try to run the app through express-app.ts, all the routes seems working (they are displayed in the terminal), but instead of returning a JSON object, in any case I'm getting this error:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="utf-8">
    <title>Error</title>
</head>

<body>
    <pre>[object Object]</pre>
</body>

</html>

Nest component versions:

- @nestjs/common: "^6.10.14"
- @nestjs/core: "^6.10.14"
- @nestjs/platform-express: "^6.10.14"
- express: "^4.16.4"
like image 295
Etienne Avatar asked Feb 16 '20 20:02

Etienne


2 Answers

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.

like image 146
Jay McDoniel Avatar answered Nov 08 '22 15:11

Jay McDoniel


@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}`);
  });
});
like image 37
Carles Capellas Avatar answered Nov 08 '22 14:11

Carles Capellas