Overview

Start a NestJS application with Fastify adapter for higher performance. Includes Swagger, versioning, CORS, Helmet, and request logging.

Features

  • Fastify adapter (higher performance)
  • Swagger API documentation
  • API versioning support
  • Class-based modular setup
  • Request/response logging

Installation

npx hanma add nest-fastify-server --framework nest

Dependencies

@nestjs/core, @nestjs/common, @nestjs/platform-fastify, @nestjs/swagger, @fastify/helmet

Code


import { ConfigService } from "@nestjs/config";
import { NestFactory } from "@nestjs/core";
import {
  DynamicModule,
  ForwardReference,
  Type,
  VersioningType,
  Logger,
  ValidationPipe,
} from "@nestjs/common";
import { DocumentBuilder, SwaggerModule } from "@nestjs/swagger";
import helmet from "@fastify/helmet";
import {
  FastifyAdapter,
  NestFastifyApplication,
} from "@nestjs/platform-fastify";
import type { ServerOptions } from "./server.interface";

type ModuleType = Type<any> | DynamicModule | ForwardReference;

export class HttpServer {
  private static logger = new Logger("HTTP");
  private static app: NestFastifyApplication;

  /**
   * Bootstrap and start the NestJS application
   */
  static async create(module: ModuleType, options: ServerOptions = {}) {
    const app = await NestFactory.create<NestFastifyApplication>(
      module,
      new FastifyAdapter(),
      {
        logger: ["log", "error", "warn", "debug", "verbose"],
      }
    );

    this.app = app;
    const config = app.get(ConfigService);
    const isDev = config.get("NODE_ENV") !== "production";

    // Apply defaults
    const opts: Required<ServerOptions> = {
      port: options.port ?? config.get<number>("PORT", 3000),
      enableVersioning: options.enableVersioning ?? false,
      defaultVersion: options.defaultVersion ?? "1",
      enableApiDocumentation: options.enableApiDocumentation ?? isDev,
      swaggerPath: options.swaggerPath ?? "api/docs",
      enableServerLogs: options.enableServerLogs ?? true,
      globalPrefix: options.globalPrefix ?? "api",
    };

    // Setup all middleware and configurations
    this.setupGlobalPrefix(opts);
    this.setupVersioning(opts);
    this.setupCors(config);
    await this.setupHelmet();
    this.setupValidation();
    this.setupSwagger(config, opts);
    this.setupRequestLogging(opts);
    this.setupGracefulShutdown();

    // Start server
    await app.listen(opts.port, "0.0.0.0");
    this.logger.log(`Server running on: ${await app.getUrl()}`);

    if (opts.enableApiDocumentation) {
      this.logger.log(`API docs: ${await app.getUrl()}/${opts.swaggerPath}`);
    }

    return app;
  }

  private static setupGlobalPrefix(opts: Required<ServerOptions>) {
    this.app.setGlobalPrefix(opts.globalPrefix);
  }

  private static setupVersioning(opts: Required<ServerOptions>) {
    if (!opts.enableVersioning) return;

    this.app.enableVersioning({
      type: VersioningType.URI,
      prefix: "v",
      defaultVersion: opts.defaultVersion,
    });

    this.logger.log(`API versioning enabled (default: v${opts.defaultVersion})`);
  }

  private static setupCors(config: ConfigService) {
    const allowedOrigins = config
      .get<string>("CORS_ORIGINS", "*")
      .split(",")
      .map((origin) => origin.trim())
      .filter(Boolean);

    const allowedMethods = config
      .get<string>("CORS_METHODS", "GET,POST,PUT,PATCH,DELETE,OPTIONS")
      .split(",")
      .filter(Boolean);

    this.app.enableCors({
      origin: (origin, callback) => {
        // Allow requests with no origin (like mobile apps or curl)
        if (!origin || allowedOrigins.includes("*") || allowedOrigins.includes(origin)) {
          callback(null, true);
        } else {
          this.logger.warn(`CORS blocked: ${origin}`);
          callback(new Error("Not allowed by CORS"), false);
        }
      },
      methods: allowedMethods,
      credentials: true,
    });
  }

  private static async setupHelmet() {
    await this.app.register(helmet, {
      contentSecurityPolicy: process.env.NODE_ENV === "production",
    });
  }

  private static setupValidation() {
    this.app.useGlobalPipes(
      new ValidationPipe({
        whitelist: true,
        forbidNonWhitelisted: true,
        transform: true,
        transformOptions: {
          enableImplicitConversion: true,
        },
      })
    );
  }

  private static setupSwagger(config: ConfigService, opts: Required<ServerOptions>) {
    if (!opts.enableApiDocumentation) return;

    const appName = process.env.npm_package_name || config.get<string>("APP_NAME", "NestJS API");
    const appVersion = process.env.npm_package_version || config.get<string>("APP_VERSION", "1.0.0");
    const appDescription = process.env.npm_package_description || config.get<string>("APP_DESCRIPTION", "API documentation");

    const documentConfig = new DocumentBuilder()
      .setTitle(appName)
      .setDescription(appDescription)
      .setVersion(appVersion)
      .addBearerAuth()
      .addTag("API")
      .build();

    const document = SwaggerModule.createDocument(this.app, documentConfig);
    SwaggerModule.setup(opts.swaggerPath, this.app, document, {
      swaggerOptions: {
        persistAuthorization: true,
        docExpansion: "none",
      },
    });
  }

  private static setupRequestLogging(opts: Required<ServerOptions>) {
    if (!opts.enableServerLogs) return;

    const instance = this.app.getHttpAdapter().getInstance();

    // Track request start time
    instance.addHook("onRequest", (req: any, _res: any, done: () => void) => {
      req.startTime = Date.now();
      done();
    });

    // Log response
    instance.addHook("onResponse", (req: any, res: any, done: () => void) => {
      const duration = Date.now() - req.startTime;
      const { method, url } = req;
      const statusCode = res.statusCode;

      const logFn =
        statusCode >= 500
          ? this.logger.error.bind(this.logger)
          : statusCode >= 400
            ? this.logger.warn.bind(this.logger)
            : this.logger.log.bind(this.logger);

      logFn(`${method} ${url} ${statusCode} - ${duration}ms`);
      done();
    });
  }

  private static setupGracefulShutdown() {
    this.app.enableShutdownHooks();

    process.on("SIGTERM", async () => {
      this.logger.log("SIGTERM signal received, closing gracefully...");
      await this.app.close();
    });

    process.on("SIGINT", async () => {
      this.logger.log("SIGINT signal received, closing gracefully...");
      await this.app.close();
    });
  }

  /**
   * Get the application instance
   */
  static getApp() {
    return this.app;
  }
}

/* USAGE in main.ts:

import { HttpServer } from "./server.setup";
import { AppModule } from "./app.module";

HttpServer.create(AppModule, {
  enableVersioning: false,
  enableApiDocumentation: true,
  enableServerLogs: true,
}).catch((err) => {
  console.error("Failed to start application:", err);
  process.exit(1);
});

*/

Usage

HttpServer.create(AppModule, {\n  enableVersioning: true,\n  enableApiDocumentation: true,\n});

Dependencies

Frequently Asked Questions

How do I install NestJS Fastify Server?
Run the following command: npx hanma add nest-fastify-server --framework nest
What dependencies are required?
This snippet requires: @nestjs/core, @nestjs/common, @nestjs/platform-fastify, @nestjs/swagger, @fastify/helmet. Make sure to install them before using the snippet.
How do I use this snippet?
Copy the code and paste it into your project. The snippet will be saved to src/main.ts.
Can I customize this snippet?
Yes! All snippets are copy-paste code that you own. Feel free to modify them to fit your needs.
Does this work with other frameworks?
This snippet is designed for the specific framework. Check the other framework pages for equivalent implementations.