Search code examples
nestjsstatic-filesfastifynestjs-fastify

How do I serve static files in NestJS using fastify?


How does one serve static files in NestJS using fastify? I can't seem to find any recent examples of setting this up properly. I have my main.ts set up like this:

main.ts

// This must be the first thing imported in the app
import 'src/tracing';

import * as winston from 'winston';
import fastifyStatic, { FastifyStaticOptions } from '@fastify/static';
import { NestFactory } from '@nestjs/core';
import {
  FastifyAdapter,
  NestFastifyApplication,
} from '@nestjs/platform-fastify';
import { path } from 'app-root-path';
import { WinstonModule } from 'nest-winston';
import { doc } from 'prettier';

import { AppModule } from 'src/app.module';

import join = doc.builders.join;

async function bootstrap() {
  const app = await NestFactory.create<NestFastifyApplication>(
    AppModule,
    new FastifyAdapter(),
    {
      logger: WinstonModule.createLogger({
        format: winston.format.combine(
          winston.format.timestamp(),
          winston.format.json(),
        ),
        transports: [new winston.transports.Console()],
      }),
      rawBody: true,
    },
  );

  await app.register(require('@fastify/static'), {
    root: require('app-root-path').resolve('/client'),
    prefix: '/client/', // optional: default '/'
  });

  // eslint-disable-next-line @typescript-eslint/ban-ts-comment
  // @ts-ignore
  app.get('/another/path', function (req, reply) {
    reply.sendFile('index.html');
  });

  app.enableShutdownHooks(); // terminus needs this to listen for SIGTERM/SIGKILL
  await app.listen(3002, '0.0.0.0');
  console.log(`Application is running on: ${await app.getUrl()}`);
}
bootstrap();

The static file I'm attempting to serve is client/index.html.

However, when I run my app I get the following error: Nest could not find /another/path element (this provider does not exist in the current context).

I've also tried setting up my app.module.ts Modules like this:

app.module.ts

@Module({
  imports: [
    ...configModules,
    ...domainModules,
    ...libraryModules,
    ServeStaticModule.forRoot({
      rootPath: require('app-root-path').resolve('/client'),
      renderPath: '/client/*',
    }),
  ],
  controllers: [AppController],
  providers: [AppService],
})

This leads to the following error:

/Users/ewu/Desktop/Projects/janus/node_modules/@nestjs/platform-fastify/node_modules/fastify/lib/route.js:286
            throw new FST_ERR_DUPLICATED_ROUTE(opts.method, opts.url)
                  ^
FastifyError: Method 'HEAD' already declared for route '/'
    at Object.addNewRoute (/Users/ewu/Desktop/Projects/janus/node_modules/@nestjs/platform-fastify/node_modules/fastify/lib/route.js:286:19)
    at Object.route (/Users/ewu/Desktop/Projects/janus/node_modules/@nestjs/platform-fastify/node_modules/fastify/lib/route.js:211:19)
    at Object.prepareRoute (/Users/ewu/Desktop/Projects/janus/node_modules/@nestjs/platform-fastify/node_modules/fastify/lib/route.js:144:18)
    at Object._head [as head] (/Users/ewu/Desktop/Projects/janus/node_modules/@nestjs/platform-fastify/node_modules/fastify/fastify.js:247:34)
    at fastifyStatic (/Users/ewu/Desktop/Projects/janus/node_modules/@fastify/static/index.js:370:17)

Here are the relevant packages and their versions:

"@nestjs/serve-static": "^3.0.0",
"fastify-static": "^4.7.0",
"fastify": "^4.8.1",
"@nestjs/platform-fastify": "^9.1.2",
"@fastify/static": "^6.0.0",

I'm using version 9.0.0 of Nest and v16.15.0 of Node.


Solution

  • You most likely have a @Get() under a @Controller() (most likely your AppController) which is binding the GET / route already. Fastify won't let you bind two handlers to the same route. Because of this, you either need to change the @Get() to have some sort of route associated with it, change the ServeStaticModule to have a different served route, or use a global prefix to modify the rest of the server routes (I believe this leaves the server static module unaffected).