CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-nestjs--platform-fastify

Fastify-based HTTP adapter for the NestJS framework, enabling high-performance HTTP server integration with NestJS applications

Pending
Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

SecuritybySnyk

Pending

The risk profile of this skill

Overview
Eval results
Files

interfaces.mddocs/

Interfaces and Configuration

Enhanced interfaces for NestJS applications with Fastify-specific functionality and configuration options for static assets, view engines, and body parsing.

Capabilities

NestFastifyApplication Interface

Enhanced NestJS application interface that provides Fastify-specific methods and functionality.

/**
 * Enhanced NestJS application interface with Fastify-specific methods
 * Extends the standard INestApplication with Fastify adapter capabilities
 */
interface NestFastifyApplication<TServer extends RawServerBase = RawServerDefault> 
  extends INestApplication<TServer> {
  
  /**
   * Returns the underlying HTTP adapter bound to the Fastify app
   */
  getHttpAdapter(): HttpServer<FastifyRequest, FastifyReply, FastifyInstance>;

  /**
   * Registers a Fastify plugin with the application
   * Wrapper around the native fastify.register() method
   * @param plugin - Fastify plugin (callback, async, or promise-wrapped)
   * @param opts - Plugin registration options
   */
  register<Options extends FastifyPluginOptions = any>(
    plugin:
      | FastifyPluginCallback<Options>
      | FastifyPluginAsync<Options>
      | Promise<{ default: FastifyPluginCallback<Options> }>
      | Promise<{ default: FastifyPluginAsync<Options> }>,
    opts?: FastifyRegisterOptions<Options>
  ): Promise<FastifyInstance>;

  /**
   * Registers custom body parsers on the fly
   * Respects the application's rawBody option
   * @param type - Content type(s) to parse
   * @param options - Body parser options
   * @param parser - Custom parser function
   */
  useBodyParser<TServer extends RawServerBase = RawServerBase>(
    type: string | string[] | RegExp,
    options?: NestFastifyBodyParserOptions,
    parser?: FastifyBodyParser<Buffer, TServer>
  ): this;

  /**
   * Configures static asset serving
   * @param options - Static asset configuration options
   */
  useStaticAssets(options: FastifyStaticOptions): this;

  /**
   * Enables Cross-Origin Resource Sharing (CORS)
   * @param options - CORS configuration options
   */
  enableCors(options?: FastifyCorsOptions): void;

  /**
   * Sets up a view engine for template rendering
   * @param options - View engine configuration (object only, string will cause error)
   */
  setViewEngine(options: FastifyViewOptions | string): this;

  /**
   * Injects a test request for testing purposes
   * Returns a chain for building the request
   */
  inject(): LightMyRequestChain;

  /**
   * Injects a test request with specific options
   * @param opts - Request options or URL string
   */
  inject(opts: InjectOptions | string): Promise<LightMyRequestResponse>;

  /**
   * Starts the application server
   * Multiple overloads for different parameter combinations
   */
  listen(
    opts: FastifyListenOptions,
    callback?: (err: Error | null, address: string) => void
  ): Promise<TServer>;
  listen(opts?: FastifyListenOptions): Promise<TServer>;
  listen(callback?: (err: Error | null, address: string) => void): Promise<TServer>;
  listen(
    port: number | string,
    callback?: (err: Error | null, address: string) => void
  ): Promise<TServer>;
  listen(
    port: number | string,
    address: string,
    callback?: (err: Error | null, address: string) => void
  ): Promise<TServer>;
  listen(
    port: number | string,
    address: string,
    backlog: number,
    callback?: (err: Error | null, address: string) => void
  ): Promise<TServer>;
}

Usage Examples:

import { NestFactory } from '@nestjs/core';
import { FastifyAdapter, NestFastifyApplication } from '@nestjs/platform-fastify';
import { AppModule } from './app.module';
import * as path from 'path';

async function bootstrap() {
  // Create application with proper typing
  const app = await NestFactory.create<NestFastifyApplication>(
    AppModule,
    new FastifyAdapter()
  );

  // Register Fastify plugins
  await app.register(require('@fastify/helmet'));
  await app.register(require('@fastify/rate-limit'), {
    max: 100,
    timeWindow: '1 minute'
  });

  // Configure body parsing
  app.useBodyParser('application/xml', { bodyLimit: 1024 * 1024 });

  // Setup static assets
  app.useStaticAssets({
    root: path.join(__dirname, '..', 'public'),
    prefix: '/static/',
  });

  // Enable CORS
  app.enableCors({
    origin: ['http://localhost:3000', 'https://example.com'],
    credentials: true
  });

  // Setup view engine
  app.setViewEngine({
    engine: {
      handlebars: require('handlebars')
    },
    templates: path.join(__dirname, '..', 'views'),
    options: {
      partials: {
        header: 'partials/header.hbs',
        footer: 'partials/footer.hbs'
      }
    }
  });

  // Start server
  await app.listen({
    port: 3000,
    host: '0.0.0.0'
  });
}

Body Parser Configuration

Type definition for configuring custom body parsers.

/**
 * Options for configuring Fastify body parsers
 * Excludes 'parseAs' property which is handled internally
 */
type NestFastifyBodyParserOptions = Omit<
  Parameters<AddContentTypeParser>[1],
  'parseAs'
>;

Common body parser options include:

interface BodyParserOptions {
  /** Maximum body size in bytes */
  bodyLimit?: number;
  /** Parser-specific configuration */
  parseAs?: 'string' | 'buffer';
  /** Content type parser configuration */
  contentTypeParser?: {
    // Parser implementation details
  };
}

Usage Examples:

// Register XML body parser
app.useBodyParser('application/xml', {
  bodyLimit: 2 * 1024 * 1024, // 2MB limit
}, (req, body, done) => {
  // Custom XML parsing logic
  const xmlParser = require('fast-xml-parser');
  try {
    const parsed = xmlParser.parse(body.toString());
    done(null, parsed);
  } catch (error) {
    done(error);
  }
});

// Register multiple content types
app.useBodyParser(['text/plain', 'text/html'], {
  bodyLimit: 1024 * 1024, // 1MB limit
}, (req, body, done) => {
  done(null, body.toString());
});

Static Assets Configuration

Configuration interface for serving static files through Fastify.

/**
 * Configuration options for static asset serving
 * Based on @fastify/static plugin options
 */
interface FastifyStaticOptions extends SendOptions {
  /** Root directory or directories for static files */
  root: string | string[] | URL | URL[];
  
  /** URL prefix for serving static files */
  prefix?: string;
  
  /** Avoid trailing slash in prefix */
  prefixAvoidTrailingSlash?: boolean;
  
  /** Whether to serve files (can be disabled for decorateReply only) */
  serve?: boolean;
  
  /** Add reply.sendFile method to reply object */
  decorateReply?: boolean;
  
  /** Hide from OpenAPI/JSON schema */
  schemaHide?: boolean;
  
  /** Function to set custom headers */
  setHeaders?: (res: SetHeadersResponse, path: string, stat: Stats) => void;
  
  /** Enable redirect from folder to folder/ */
  redirect?: boolean;
  
  /** Enable wildcard matching */
  wildcard?: boolean;
  
  /** Enable/configure directory listing */
  list?: boolean | ListOptionsJsonFormat | ListOptionsHtmlFormat;
  
  /** Function to filter allowed paths */
  allowedPath?: (
    pathName: string,
    root: string,
    request: FastifyRequest
  ) => boolean;
  
  /** Look for pre-compressed files (.gz, .br) */
  preCompressed?: boolean;
  
  /** Route constraints for static serving */
  constraints?: RouteOptions['constraints'];
  
  // Inherited from SendOptions
  acceptRanges?: boolean;
  cacheControl?: boolean;
  dotfiles?: 'allow' | 'deny' | 'ignore';
  etag?: boolean;
  extensions?: string[];
  immutable?: boolean;
  index?: string[] | string | false;
  lastModified?: boolean;
  maxAge?: string | number;
}

interface SetHeadersResponse {
  getHeader: FastifyReply['getHeader'];
  setHeader: FastifyReply['header'];
  readonly filename: string;
  statusCode: number;
}

interface ListOptionsJsonFormat {
  format: 'json';
  names: string[];
  extendedFolderInfo?: boolean;
  jsonFormat?: 'names' | 'extended';
  render?: ListRender;
}

interface ListOptionsHtmlFormat {
  format: 'html';
  names: string[];
  extendedFolderInfo?: boolean;
  jsonFormat?: 'names' | 'extended';
  render: ListRender;
}

interface ListRender {
  (dirs: ListDir[], files: ListFile[]): string;
}

Usage Examples:

// Basic static assets
app.useStaticAssets({
  root: path.join(__dirname, '..', 'public'),
  prefix: '/static/',
});

// Advanced configuration
app.useStaticAssets({
  root: [
    path.join(__dirname, '..', 'public'),
    path.join(__dirname, '..', 'uploads')
  ],
  prefix: '/assets/',
  maxAge: '1d',
  etag: true,
  lastModified: true,
  immutable: true,
  preCompressed: true,
  setHeaders: (res, path, stat) => {
    if (path.endsWith('.pdf')) {
      res.setHeader('Content-Type', 'application/pdf');
      res.setHeader('Content-Security-Policy', "default-src 'self'");
    }
  },
  allowedPath: (pathName, root, request) => {
    // Only allow access to certain files based on user permissions
    return !pathName.includes('private') || request.headers.authorization;
  },
  list: {
    format: 'json',
    names: ['index.html'],
    extendedFolderInfo: true
  }
});

// Serving with constraints
app.useStaticAssets({
  root: path.join(__dirname, '..', 'admin-assets'),
  prefix: '/admin/',
  constraints: {
    host: 'admin.example.com'
  }
});

View Engine Configuration

Configuration interface for template engines and view rendering.

/**
 * Configuration options for view engine setup
 * Based on @fastify/view plugin options
 */
interface FastifyViewOptions {
  /** Template engine configuration */
  engine: {
    /** EJS template engine */
    ejs?: any;
    /** Eta template engine */
    eta?: any;
    /** Nunjucks template engine */
    nunjucks?: any;
    /** Pug template engine */
    pug?: any;
    /** Handlebars template engine */
    handlebars?: any;
    /** Mustache template engine */
    mustache?: any;
    /** Art Template engine */
    'art-template'?: any;
    /** Twig template engine */
    twig?: any;
    /** Liquid template engine */
    liquid?: any;
    /** doT template engine */
    dot?: any;
  };
  
  /** Template directory path(s) */
  templates?: string | string[];
  
  /** Include file extension in template names */
  includeViewExtension?: boolean;
  
  /** Engine-specific options */
  options?: object;
  
  /** Character encoding for templates */
  charset?: string;
  
  /** Maximum number of cached templates */
  maxCache?: number;
  
  /** Production mode optimizations */
  production?: boolean;
  
  /** Default context for all templates */
  defaultContext?: object;
  
  /** Default layout template */
  layout?: string;
  
  /** Root directory for templates */
  root?: string;
  
  /** Default view file extension */
  viewExt?: string;
  
  /** Property name for reply.view method */
  propertyName?: string;
  
  /** Property name for async reply method */
  asyncProperyName?: string;
}

Usage Examples:

// Handlebars setup
app.setViewEngine({
  engine: {
    handlebars: require('handlebars')
  },
  templates: path.join(__dirname, '..', 'views'),
  options: {
    partials: {
      header: 'partials/header.hbs',
      footer: 'partials/footer.hbs',
      sidebar: 'partials/sidebar.hbs'
    },
    helpers: {
      formatDate: (date) => new Date(date).toLocaleDateString(),
      uppercase: (str) => str.toUpperCase()
    }
  },
  layout: 'layouts/main.hbs',
  viewExt: 'hbs',
  defaultContext: {
    siteName: 'My Application',
    year: new Date().getFullYear()
  }
});

// Pug setup with production optimizations
app.setViewEngine({
  engine: {
    pug: require('pug')
  },
  templates: path.join(__dirname, '..', 'views'),
  production: process.env.NODE_ENV === 'production',
  maxCache: 100,
  options: {
    pretty: process.env.NODE_ENV !== 'production',
    cache: process.env.NODE_ENV === 'production',
    basedir: path.join(__dirname, '..', 'views')
  }
});

// Multiple template directories
app.setViewEngine({
  engine: {
    ejs: require('ejs')
  },
  templates: [
    path.join(__dirname, '..', 'views'),
    path.join(__dirname, '..', 'shared-templates')
  ],
  options: {
    delimiter: '?',
    openDelimiter: '<',
    closeDelimiter: '>',
    cache: true
  }
});

Testing Interfaces

Interfaces for testing with light-my-request integration.

/**
 * Options for injecting test requests
 */
interface InjectOptions {
  method?: string;
  url?: string;
  path?: string;
  query?: string | Record<string, any>;
  payload?: any;
  headers?: Record<string, string>;
  cookies?: Record<string, string>;
  remoteAddress?: string;
  server?: any;
  simulate?: {
    end?: boolean;
    split?: boolean;
    error?: boolean;
    close?: boolean;
  };
  validate?: boolean;
  authority?: string;
}

/**
 * Chain interface for building test requests
 */
interface LightMyRequestChain {
  get(url: string): LightMyRequestChain;
  post(url: string): LightMyRequestChain;
  put(url: string): LightMyRequestChain;
  patch(url: string): LightMyRequestChain;
  delete(url: string): LightMyRequestChain;
  head(url: string): LightMyRequestChain;
  options(url: string): LightMyRequestChain;
  headers(headers: Record<string, string>): LightMyRequestChain;
  payload(payload: any): LightMyRequestChain;
  query(query: Record<string, any>): LightMyRequestChain;
  cookies(cookies: Record<string, string>): LightMyRequestChain;
  end(): Promise<LightMyRequestResponse>;
}

/**
 * Response from injected test requests
 */
interface LightMyRequestResponse {
  statusCode: number;
  statusMessage: string;
  headers: Record<string, string>;
  rawPayload: Buffer;
  payload: string;
  body: string;
  json(): any;
  cookies: Array<{
    name: string;
    value: string;
    path?: string;
    domain?: string;
    expires?: Date;
    httpOnly?: boolean;
    secure?: boolean;
    sameSite?: string;
  }>;
}

Usage Examples:

// In test files
describe('ProductsController', () => {
  let app: NestFastifyApplication;

  beforeEach(async () => {
    const module = await Test.createTestingModule({
      controllers: [ProductsController],
    }).compile();

    app = module.createNestApplication<NestFastifyApplication>(
      new FastifyAdapter()
    );
    await app.init();
  });

  it('should return products', async () => {
    const response = await app.inject({
      method: 'GET',
      url: '/products',
      query: { page: '1', limit: '10' }
    });

    expect(response.statusCode).toBe(200);
    expect(response.json()).toEqual({
      products: expect.any(Array),
      total: expect.any(Number)
    });
  });

  it('should create product', async () => {
    const productData = {
      name: 'Test Product',
      price: 29.99,
      category: 'electronics'
    };

    const response = await app.inject()
      .post('/products')
      .headers({ 'content-type': 'application/json' })
      .payload(productData)
      .end();

    expect(response.statusCode).toBe(201);
    expect(response.json()).toMatchObject(productData);
  });
});

docs

decorators.md

fastify-adapter.md

index.md

interfaces.md

tile.json