#How to register Express-specific routes after Nest routes?

9 messages · Page 1 of 1 (latest)

ashen jasper
#

Is there a way to attach routes to the underlying Express app after all Nest routes have been registered?

I have a request handler (Remix) that I'd like to include with my Nest app.

If I add the handler in my bootstap function, it ends up taking over all the routing (I assume because Nest registers its routes later). Here's a simple example:

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  const httpAdapter = app.getHttpAdapter().getInstance() as express.Express;
  httpAdapter.all('*', createRequestHandler());
  app.listen(3000);
}
bootstrap();

Note: The above is using the all function from Express: https://expressjs.com/en/4x/api.html#app.all

#

How to register Express-specific routes after Nest routes?

glad folio
#

I'm not familiar with this method. Can you use it like this in express?

app.use(cookieParser());
ashen jasper
#

It looks like the request handler can be used as a middleware instead. Is there a way to define the middleware after all other Nest middleware/routes?

#

Here's the same thing in a middleware, but again, it takes precedence over all Nest routes. Whereas, I want Nest to take precedence and if there is no existing route, use this other handler instead.

import { Injectable, NestMiddleware } from '@nestjs/common';
import { createRequestHandler } from '@remix-run/express';
import { Request, Response, NextFunction } from 'express';
import path from 'path';

const BUILD_DIR = path.join(process.cwd(), 'build');

@Injectable()
export class RemixMiddleware implements NestMiddleware {
  use(req: Request, res: Response, next: NextFunction) {
    return createRequestHandler({ build: require(BUILD_DIR) })(req, res, next);
  }
}
ashen jasper
#

The only way I've found so far to accomplish this is to use an exception filter, but this definitely feels wrong. I wonder if there's a better supported way to do this.

import {
  ArgumentsHost,
  Catch,
  ExceptionFilter,
  NotFoundException,
} from '@nestjs/common';
import { NextFunction, Request, Response } from 'express';
import { createRequestHandler } from '@remix-run/express';
import path from 'path';

@Catch(NotFoundException)
export class RemixExceptionFilter implements ExceptionFilter {
  catch(exception: NotFoundException, host: ArgumentsHost) {
    const ctx = host.switchToHttp();
    const response = ctx.getResponse<Response>();
    const request = ctx.getRequest<Request>();
    const next = ctx.getRequest<NextFunction>();

    const BUILD_DIR = path.join(process.cwd(), 'build');
    const env = process.env.NODE_ENV;

    return createRequestHandler({ build: require(BUILD_DIR), mode: env })(
      request,
      response,
      next,
    );
  }
}
ashen jasper
#

Okay, figured a much better solution. Controllers and modules are registered in order, so we can specify one at the end as a catch-all. For example, by using @Controller('*')

import { Controller, Get, Res, Req, Next } from '@nestjs/common';
import path from 'path';
import { NextFunction, Request, Response } from 'express';
import { createRequestHandler } from '@remix-run/express';

@Controller('*')
export class RemixController {
  @Get()
  getHello(
    @Req() request: Request,
    @Res() response: Response,
    @Next() next: NextFunction,
  ) {
    const BUILD_DIR = path.join(process.cwd(), 'build');
    const env = process.env.NODE_ENV;

    if (env === 'production') {
      createRequestHandler({ build: require(BUILD_DIR), mode: env })(
        request,
        response,
        next,
      );
    } else {
      for (const key in require.cache) {
        if (key.startsWith(BUILD_DIR)) {
          delete require.cache[key];
        }
      }
      const requestHandler = createRequestHandler({
        build: require(BUILD_DIR),
        mode: env,
      });
      requestHandler(request, response, next);
    }
  }
}
import { Module } from '@nestjs/common';
import { RemixController } from './remix.controller';

@Module({
  imports: [],
  controllers: [RemixController],
  providers: [],
})
export class RemixModule {}
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { RemixModule } from './remix/remix.module';

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

It turns out that this breaks serving static files if you do it from the Nest side via ServeStaticModule