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-form-data

v11.0.1

Published

NestJS middleware for handling multipart/form-data, which is primarily used for uploading files

Readme

npm version CI License

nestjs-form-data

An object-oriented approach to handling multipart/form-data in NestJS. Uploaded files become typed class instances with built-in validation, automatic cleanup, and pluggable storage — no manual stream wiring required.

Why nestjs-form-data?

NestJS ships with Multer-based file upload, but it works outside the DTO validation flow — you handle files through @UploadedFile() separately from @Body(), losing the single-source-of-truth that DTOs provide.

nestjs-form-data takes a different approach: files are first-class properties on your DTO. They arrive as typed objects (MemoryStoredFile, FileSystemStoredFile, or your own custom class), validated with the same decorators you already use for strings and numbers:

export class CreatePostDto {
  @IsString()
  title: string;

  @IsFile()
  @MaxFileSize(5e6)
  @HasMimeType(['image/jpeg', 'image/png'])
  cover: MemoryStoredFile;
}

No @UploadedFile(), no separate pipes, no manual cleanup. Just a DTO.

Key features

  • Files as typed objects — each uploaded file is an instance of a StoredFile class with properties like size, mimeType, extension, originalName, and a reliable buffer or path
  • Declarative validation — validate file size, MIME type, and extension with decorators, including support for arrays ({ each: true })
  • Reliable MIME detection — uses file-type to read the file's magic number, falling back to the content-type header only when needed
  • Nested objects — fields with bracket notation (photos[0][name]) are parsed into proper nested structures
  • Pluggable storage — choose MemoryStoredFile for speed, FileSystemStoredFile for large files, or extend StoredFile to write your own (S3, GCS, etc.)
  • Automatic cleanup — temporary files are deleted after the request completes (configurable per success/failure)
  • Express and Fastify support
  • NestJS 7 – 11 compatible

Installation

npm install nestjs-form-data

This module requires class-validator and class-transformer as peer dependencies:

npm install class-validator class-transformer

Register a global validation pipe in main.ts:

import { ValidationPipe } from '@nestjs/common';

app.useGlobalPipes(
  new ValidationPipe({
    transform: true, // recommended to avoid issues with file array transformations
  }),
);

Add the module to your application:

import { NestjsFormDataModule } from 'nestjs-form-data';

@Module({
  imports: [NestjsFormDataModule],
})
export class AppModule {}

Quick start

Apply @FormDataRequest() to your controller method and define a DTO:

import { Controller, Post, Body } from '@nestjs/common';
import { FormDataRequest, MemoryStoredFile, IsFile, MaxFileSize, HasMimeType } from 'nestjs-form-data';

class UploadAvatarDto {
  @IsFile()
  @MaxFileSize(1e6)
  @HasMimeType(['image/jpeg', 'image/png'])
  avatar: MemoryStoredFile;
}

@Controller('users')
export class UsersController {
  @Post('avatar')
  @FormDataRequest()
  uploadAvatar(@Body() dto: UploadAvatarDto) {
    // dto.avatar is a MemoryStoredFile instance
    console.log(dto.avatar.originalName); // "photo.jpg"
    console.log(dto.avatar.size);         // 94521
    console.log(dto.avatar.mimeType);     // "image/jpeg"
    console.log(dto.avatar.buffer);       // <Buffer ff d8 ff ...>
  }
}

That's it. The file is parsed, validated, and available as a typed object on your DTO.

Fastify

Install @fastify/multipart and register it:

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

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

Everything else works the same — DTOs, decorators, and storage types are platform-agnostic.

File storage types

Memory storage

avatar: MemoryStoredFile;

The file is loaded entirely into RAM as a Buffer. Fast, but not suitable for large files.

File system storage

avatar: FileSystemStoredFile;

The file is written to a temporary directory on disk and available via file.path during request processing. Automatically deleted when the request completes.

Custom storage

Extend the StoredFile abstract class to implement your own storage (e.g., stream directly to S3):

import { StoredFile } from 'nestjs-form-data';

export class S3StoredFile extends StoredFile {
  s3Key: string;
  size: number;

  static async create(meta, stream, config): Promise<S3StoredFile> {
    // upload stream to S3, return instance
  }

  async delete(): Promise<void> {
    // delete from S3
  }
}

Then use it: @FormDataRequest({ storage: S3StoredFile })

Configuration

Static

@Module({
  imports: [
    NestjsFormDataModule.config({
      storage: MemoryStoredFile,
      isGlobal: true,
      limits: {
        fileSize: 5e6, // 5 MB
        files: 10,
      },
    }),
  ],
})
export class AppModule {}

Async

NestjsFormDataModule.configAsync({
  imports: [ConfigModule],
  useFactory: async (configService: ConfigService) => ({
    storage: MemoryStoredFile,
    limits: {
      files: configService.get<number>('MAX_FILES'),
    },
  }),
  inject: [ConfigService],
});

You can also use useClass or useExisting patterns — see NestJS async providers for details:

// useClass — creates a new instance
NestjsFormDataModule.configAsync({
  useClass: MyFormDataConfigService,
});

// useExisting — reuses an imported provider
NestjsFormDataModule.configAsync({
  imports: [MyConfigModule],
  useExisting: MyFormDataConfigService,
});

Where the config service implements:

export class MyFormDataConfigService implements NestjsFormDataConfigFactory {
  configAsync(): FormDataInterceptorConfig {
    return {
      storage: FileSystemStoredFile,
      fileSystemStoragePath: '/tmp/nestjs-fd',
    };
  }
}

Method-level override

Override global config for a specific endpoint:

@Post('upload')
@FormDataRequest({ storage: FileSystemStoredFile })
upload(@Body() dto: UploadDto) {}

Configuration options

| Option | Type | Default | Description | |--------|------|---------|-------------| | storage | Type<StoredFile> | MemoryStoredFile | Storage class for uploaded files | | isGlobal | boolean | false | Make the module available to all submodules | | fileSystemStoragePath | string | /tmp/nestjs-tmp-storage | Temp directory for FileSystemStoredFile | | cleanupAfterSuccessHandle | boolean | true | Delete files after successful request | | cleanupAfterFailedHandle | boolean | true | Delete files after failed request | | awaitCleanup | boolean | true | Wait for file cleanup before sending response. Set to false for faster responses (cleanup runs in the background) | | limits | object | {} | Busboy limits: fileSize, files, fields, parts, headerPairs |

Validation decorators

All validators work with { each: true } for arrays of files.

@IsFile / @IsFiles

Checks if the value is an uploaded file (instance of StoredFile).

@IsFile()
avatar: MemoryStoredFile;

@IsFiles()
photos: MemoryStoredFile[];

@MaxFileSize / @MinFileSize

File size constraints in bytes.

@MaxFileSize(5e6)          // max 5 MB
@MinFileSize(1024)         // min 1 KB
avatar: MemoryStoredFile;

@HasMimeType

Validate MIME type. Supports exact strings, wildcard patterns, and regular expressions.

// exact match
@HasMimeType(['image/jpeg', 'image/png'])

// wildcard — matches any image type
@HasMimeType('image/*')

// regex
@HasMimeType([/image\/.*/])

MIME type detection priority:

  1. Magic number (via file-type) — reads binary data, reliable
  2. Content-Type header (via busboy) — client-provided, can be spoofed

To enforce that the MIME type comes from a specific source:

import { MetaSource } from 'nestjs-form-data';

@HasMimeType(['image/jpeg'], MetaSource.bufferMagicNumber)  // only trust magic number

Access the source at runtime via file.mimeTypeWithSource.

@HasExtension

Validate file extension. Same source priority and strict mode as @HasMimeType.

@HasExtension(['jpg', 'png'])

// strict — only trust extension from magic number detection
@HasExtension(['jpg'], MetaSource.bufferMagicNumber)

Access the source at runtime via file.extensionWithSource.

Examples

Single file with FileSystemStoredFile

Controller:

import { FileSystemStoredFile, FormDataRequest } from 'nestjs-form-data';

@Controller()
export class NestjsFormDataController {
  @Post('load')
  @FormDataRequest({ storage: FileSystemStoredFile })
  getHello(@Body() testDto: FormDataTestDto): void {
    console.log(testDto);
  }
}

DTO:

import { FileSystemStoredFile, HasMimeType, IsFile, MaxFileSize } from 'nestjs-form-data';

export class FormDataTestDto {
  @IsFile()
  @MaxFileSize(1e6)
  @HasMimeType(['image/jpeg', 'image/png'])
  avatar: FileSystemStoredFile;
}

Send request (via Insomnia):

image

Array of files

DTO:

import { FileSystemStoredFile, HasMimeType, IsFiles, MaxFileSize } from 'nestjs-form-data';

export class FormDataTestDto {
  @IsFiles()
  @MaxFileSize(1e6, { each: true })
  @HasMimeType(['image/jpeg', 'image/png'], { each: true })
  avatars: FileSystemStoredFile[];
}

Send request (via Insomnia):

image

Mixed fields and files

export class CreateProductDto {
  @IsString()
  name: string;

  @IsNumber()
  @Type(() => Number)
  price: number;

  @IsFile()
  @MaxFileSize(5e6)
  @HasMimeType(['image/*'])
  image: MemoryStoredFile;
}

Changelog

See CHANGELOG.md.

License

MIT