NestJS FormData Handling

raw JSON →
11.0.1 verified Thu Apr 23 auth: no javascript

nestjs-form-data provides an object-oriented approach to managing `multipart/form-data` requests in NestJS, specifically designed for file uploads and form field serialization. Unlike the built-in Multer integration, it allows files to be treated as first-class properties within Data Transfer Objects (DTOs), enabling declarative validation with `class-validator` and `class-transformer`. The current stable version is `11.0.1`, with active development indicated by recent major releases addressing features, fixes, and security concerns. Key differentiators include automatic cleanup of temporary files, pluggable storage options (memory, file system, or custom), reliable MIME type detection using magic numbers, and full support for nested objects in form data. It is compatible with NestJS versions 7 through 11 and supports both Express and Fastify platforms.

error TypeError: Cannot read properties of undefined (reading 'originalName')
cause The DTO property for the file (e.g., `dto.avatar`) is `undefined` or not a `StoredFile` instance, often due to missing `@FormDataRequest()` decorator or incorrect `ValidationPipe` configuration.
fix
Verify that @FormDataRequest() is applied to your controller method and that app.useGlobalPipes(new ValidationPipe({ transform: true })) is configured in main.ts.
error HttpException: Request must have a Content-Type of multipart/form-data
cause The client making the request is not sending the `Content-Type` header as `multipart/form-data`, which is necessary for file uploads.
fix
Ensure your client-side code (e.g., HTML form, Axios, Fetch API) correctly sets Content-Type: multipart/form-data and sends the data in the appropriate format. For HTML forms, enctype="multipart/form-data" is needed.
error BadRequestException: Invalid mime type
cause The MIME type of the uploaded file does not match any of the allowed types specified in the `@HasMimeType()` decorator on your DTO property.
fix
Check the mimeTypes array passed to @HasMimeType() in your DTO to ensure it includes the expected MIME types for the files being uploaded. Also, review the strictSource option if on v11.0.0+.
error BadRequestException: File size exceeds the allowed limit
cause The size of the uploaded file is larger than the maximum allowed size configured in the `@MaxFileSize()` decorator.
fix
Increase the maxSize value (in bytes) provided to the @MaxFileSize() decorator in your DTO or adjust the maxFileSize global configuration in NestjsFormDataModule.config().
breaking The configuration option `autoDeleteFile` was deprecated in favor of more granular control. It has been replaced by two separate fields: `cleanupAfterSuccessHandle` and `cleanupAfterFailedHandle`.
fix Update your `NestjsFormDataModule.config()` to use `cleanupAfterSuccessHandle: boolean` and `cleanupAfterFailedHandle: boolean` instead of `autoDeleteFile`.
breaking A prototype pollution vulnerability via multipart field names (`__proto__[key]`) was fixed. The internal form-data result object is now created with `Object.create(null)` to prevent inherited properties from being modified.
fix Upgrade to `v11.0.0` or later to mitigate the prototype pollution vulnerability. This change might subtly affect applications that previously relied on inherited properties on the parsed form data object (though this is highly unlikely and discouraged).
gotcha The `HasMimeType` validator's `strictSource` parameter was silently ignored in versions prior to `v11.0.0`. It now correctly enforces the source for MIME type detection, which might alter validation behavior for existing configurations.
fix Review your usage of `@HasMimeType` decorators with the `strictSource` option after upgrading to `v11.0.0+` to ensure desired validation behavior. Adjust the `strictSource` setting if necessary.
breaking A race condition where file cleanup (using `deleteFiles()`) was not consistently awaited before sending the response was fixed. Cleanup is now properly awaited by default. A new `awaitCleanup` configuration option (default `true`) was added to allow for fire-and-forget cleanup for faster response times.
fix Upgrade to `v11.0.1`. If you need to revert to a fire-and-forget cleanup for faster responses, configure `awaitCleanup: false` in `NestjsFormDataModule.config()`.
gotcha A global `ValidationPipe` with `transform: true` is crucial for `nestjs-form-data` to correctly transform incoming request bodies into DTO instances and hydrate `StoredFile` objects, especially for file arrays. Without it, file properties might be `undefined` or plain objects.
fix Ensure your `main.ts` includes `app.useGlobalPipes(new ValidationPipe({ transform: true }))`.
gotcha This package explicitly requires Node.js version 20 or higher. Using older versions may lead to unexpected behavior or installation issues.
fix Upgrade your Node.js environment to version 20 or newer.
npm install nestjs-form-data
yarn add nestjs-form-data
pnpm add nestjs-form-data

This quickstart demonstrates how to set up `nestjs-form-data` to handle a single file upload along with a text field. It defines a DTO with file-specific validation decorators, applies the `@FormDataRequest()` decorator to a controller method, and configures a global `ValidationPipe` for DTO transformation.

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

// 1. Define your DTO for file upload and form fields
class UploadAvatarDto {
  @IsFile()
  @MaxFileSize(1e6, { message: 'Avatar file size must not exceed 1MB' })
  @HasMimeType(['image/jpeg', 'image/png'], { message: 'Avatar must be a JPEG or PNG image' })
  avatar: MemoryStoredFile;

  @Body('userId')
  userId: string;
}

// 2. Create your controller
@Controller('users')
export class UsersController {
  @Post('avatar')
  @FormDataRequest() // Apply the decorator to enable form data parsing
  uploadAvatar(@Body() dto: UploadAvatarDto) {
    // dto.avatar is now a MemoryStoredFile instance
    console.log(`User ${dto.userId} uploaded avatar:`);
    console.log(`Original Name: ${dto.avatar.originalName}`);
    console.log(`Size: ${dto.avatar.size} bytes`);
    console.log(`MIME Type: ${dto.avatar.mimeType}`);
    console.log(`Buffer length: ${dto.avatar.buffer.length}`);

    // In a real application, you would save dto.avatar.buffer to storage (e.g., S3, local disk)
    return { message: `Avatar for user ${dto.userId} uploaded successfully!` };
  }
}

// 3. Register the module and global validation pipe
@Module({
  imports: [
    NestjsFormDataModule.config({
      is// Enable file system storage by default, or keep MemoryStoredFile as default
      // storage: FileSystemStoredFile, 
    }),
  ],
  controllers: [UsersController],
})
export class AppModule {}

// 4. Bootstrap your NestJS application
async function bootstrap() {
  const app = await NestFactory.create(AppModule);

  app.useGlobalPipes(
    new ValidationPipe({
      transform: true, // Crucial for DTO transformation and file object hydration
      whitelist: true, // Recommended for security
      forbidNonWhitelisted: true, // Recommended for security
    }),
  );

  await app.listen(3000);
  console.log('Application is running on: http://localhost:3000');
}
bootstrap();