npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

nestjs-fastify-upload

v1.0.3

Published

A high-performance, secure, and minimal fastify-multipart upload plugin for NestJS

Readme

nestjs-fastify-upload

A high-performance, secure, and minimal fastify-multipart upload plugin for NestJS. Engineered for scale and high concurrency, with optional in-memory processing for cloud-native workflows.

Supports ALL file types (Images, Videos, PDFs, CSVs, Binaries, etc.). Stream directly to disk or keep in memory for cloud uploads.

Features

  • Extreme Performance: Low latency, streams directly to disk avoiding RAM allocations efficiently handling massive concurrent uploads.
  • Memory Mode: storage: 'memory' returns a Buffer — no disk I/O. Ideal for uploading directly to R2, S3, GDrive, etc.
  • Image Optimization: Built-in Sharp integration for resize, format conversion, and quality optimization.
  • Provider Hook: onFile callback lets you wire your own cloud provider logic after file processing.
  • Secure by Default: Validates MIME types, file extensions, and file sizes strictly.
  • Minimal Dependencies: Only @fastify/multipart and @fastify/busboy. Sharp is optional.
  • Easy NestJS Integration: Custom intuitive decorators for handling @UploadFile() and @UploadFiles().
  • Form fields → req.body: After consuming the file stream(s), non-file multipart fields are merged into req.body for @Body().

Installation

npm install nestjs-fastify-upload

For image optimization, also install Sharp:

npm install sharp

Quick Start

Setup main.ts

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

async function bootstrap() {
  const app = await NestFactory.create<NestFastifyApplication>(
    AppModule,
    new FastifyAdapter()
  );

  await app.register(multipart);
  await app.listen(3000);
}
bootstrap();

Controller Integration

import { Controller, Post, UseInterceptors } from '@nestjs/common';
import {
  FileInterceptor, FilesInterceptor,
  UploadFile, UploadFiles,
  UploadedFileResult
} from 'nestjs-fastify-upload';

@Controller('upload')
export class UploadController {

  // Disk mode (default) — streams directly to disk
  @Post('profile-picture')
  @UseInterceptors(FileInterceptor('file', {
    dest: './storage/images',
    allowedMimeTypes: ['image/jpeg', 'image/png', 'image/webp'],
    optimizeImage: { w: 400, h: 400, quality: 80 },
    maxFileSize: 5 * 1024 * 1024,
  }))
  uploadProfilePicture(@UploadFile() file: UploadedFileResult) {
    return { message: 'File saved to disk', file };
  }

  // Memory mode — returns Buffer, no disk writes
  @Post('upload-to-cloud')
  @UseInterceptors(FileInterceptor('file', {
    storage: 'memory',
    allowedMimeTypes: ['image/png', 'application/pdf'],
    maxFileSize: 10 * 1024 * 1024,
  }))
  async uploadToCloud(@UploadFile() file: UploadedFileResult) {
    // file.buffer is available — send directly to R2/S3/GDrive
    await this.myCloudService.upload(file.buffer, file.name);
    return { message: 'Uploaded to cloud', name: file.name, checksum: file.checksum };
  }

  // Memory mode + onFile hook — auto-send to provider
  @Post('auto-upload')
  @UseInterceptors(FileInterceptor('file', {
    storage: 'memory',
    onFile: async (file) => {
      const result = await myS3Provider.upload(file);
      return { url: result.url, providerResult: result };
    },
  }))
  autoUpload(@UploadFile() file: UploadedFileResult) {
    return { url: file.url, providerResult: file.providerResult };
  }

  // Multiple files
  @Post('gallery')
  @UseInterceptors(FilesInterceptor('files', {
    dest: './storage/gallery',
    maxFiles: 5,
    maxFileSize: 10 * 1024 * 1024,
  }))
  uploadGallery(@UploadFiles() files: UploadedFileResult[]) {
    return { message: 'Files saved!', files };
  }
}

Options

| Option | Type | Default | Description | |---|---|---|---| | dest | string | './' | Target directory (required for storage: 'disk'). Created automatically. | | storage | 'disk' \| 'memory' | 'disk' | 'disk' streams to disk (zero RAM). 'memory' returns a Buffer. | | maxFileSize | number | 5MB | Maximum bytes per file. | | allowedExtensions | string[] | — | Allowed file extensions, e.g. ['.jpg', '.pdf']. | | allowedMimeTypes | string[] | — | Allowed MIME types, e.g. ['image/png']. | | optimizeImage | boolean \| OptimizeImageOptions | — | Optimize images with Sharp. Requires sharp to be installed. | | ~~resizeImage~~ | { w, h } | — | Deprecated. Use optimizeImage instead. | | maxFiles | number | 10 | Max files for @UploadFiles(). | | onFile | (file: FilePayload) => Promise<Partial<UploadedFileResult>> | — | Post-processing hook. Only runs in storage: 'memory' mode. |

OptimizeImageOptions

| Option | Type | Default | Description | |---|---|---|---| | w | number | — | Target width (cover crop). | | h | number | — | Target height (cover crop). | | quality | number | 80 | Output quality (1–100). | | format | 'jpeg' \| 'png' \| 'webp' \| 'avif' | Same as input | Output format. | | withoutEnlargement | boolean | true | Don't upscale images smaller than target. |

Storage Modes

storage: 'disk' (default)

Files are streamed directly to disk using Node.js pipelines. Memory stays under 20MB even for multi-GB files. SHA-256 is computed incrementally during streaming.

When optimizeImage is enabled in disk mode, Sharp reads the temp file, optimizes it, and writes the final result — no large buffers in RAM.

storage: 'memory'

The entire file is collected into a Buffer. After processing, the Buffer is returned in UploadedFileResult.buffer. No disk writes occur.

Best for:

  • Uploading directly to cloud providers (R2, S3, GDrive, etc.)
  • Temporary processing where disk I/O is undesirable
  • Small to medium files

Provider Integration

onFile Hook

The onFile callback receives a FilePayload after the file is fully processed and lets you merge extra data into the result.

@UseInterceptors(FileInterceptor('file', {
  storage: 'memory',
  onFile: async (file: FilePayload) => {
    const { url, id } = await r2Provider.upload(file.buffer, file.filename);
    return { url, providerResult: { id } };
  },
}))

UploadProvider Interface

For reusable provider logic across projects, implement the UploadProvider interface:

import { UploadProvider, FilePayload } from 'nestjs-fastify-upload';

class MyS3Provider implements UploadProvider {
  readonly name = 's3';

  async upload(file: FilePayload): Promise<Record<string, any>> {
    const result = await s3Client.send(new PutObjectCommand({
      Bucket: process.env.S3_BUCKET,
      Key: file.filename,
      Body: file.buffer,
    }));
    return { url: `https://s3.amazonaws.com/${process.env.S3_BUCKET}/${file.filename}`, etag: result.ETag };
  }

  async delete(id: string): Promise<void> {
    await s3Client.send(new DeleteObjectCommand({ Bucket: process.env.S3_BUCKET, Key: id }));
  }
}

Types

type UploadStorage = 'disk' | 'memory';

interface FilePayload {
  buffer: Buffer;
  filename: string;
  mimetype: string;
  ext: string;
  size: number;
  checksum: string;
}

interface UploadProvider {
  readonly name: string;
  upload(file: FilePayload): Promise<Record<string, any>>;
  delete?(id: string): Promise<void>;
}

interface OptimizeImageOptions {
  w?: number;
  h?: number;
  quality?: number;
  format?: 'jpeg' | 'png' | 'webp' | 'avif';
  withoutEnlargement?: boolean;
}

interface UploadOptions {
  dest?: string;
  storage?: UploadStorage;
  maxFileSize?: number;
  allowedExtensions?: string[];
  allowedMimeTypes?: string[];
  /** @deprecated Use optimizeImage */
  resizeImage?: { w: number; h: number };
  optimizeImage?: boolean | OptimizeImageOptions;
  maxFiles?: number;
  onFile?: (file: FilePayload) => Promise<Partial<UploadedFileResult>>;
}

interface UploadedFileResult {
  name: string;
  path?: string;        // Set in disk mode
  ext: string;
  size: number;
  checksum: string;
  buffer?: Buffer;      // Set in memory mode
  url?: string;          // Set by onFile
  providerResult?: Record<string, any>; // Set by onFile
}

Architecture & Security

  • Asynchronous Disk Piping: Direct Node Stream pipelines push multipart TCP packets to disk without buffering in RAM.
  • Memory Mode: Collects chunks into a Buffer — zero disk I/O. Ideal for cloud-native pipelines.
  • Auto-Cleanup: Partial files from aborted connections or validation failures are safely unlinked.
  • SHA-256 Verification: Every file is checksummed, whether streamed to disk or kept in memory.

Author

@royaltics.solutions