NestJS FormData Handling
raw JSON →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.
Common errors
error TypeError: Cannot read properties of undefined (reading 'originalName') ↓
@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 ↓
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 ↓
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 ↓
maxSize value (in bytes) provided to the @MaxFileSize() decorator in your DTO or adjust the maxFileSize global configuration in NestjsFormDataModule.config(). Warnings
breaking The configuration option `autoDeleteFile` was deprecated in favor of more granular control. It has been replaced by two separate fields: `cleanupAfterSuccessHandle` and `cleanupAfterFailedHandle`. ↓
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. ↓
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. ↓
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. ↓
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. ↓
gotcha This package explicitly requires Node.js version 20 or higher. Using older versions may lead to unexpected behavior or installation issues. ↓
Install
npm install nestjs-form-data yarn add nestjs-form-data pnpm add nestjs-form-data Imports
- NestjsFormDataModule wrong
const { NestjsFormDataModule } = require('nestjs-form-data')correctimport { NestjsFormDataModule } from 'nestjs-form-data' - FormDataRequest wrong
import FormDataRequest from 'nestjs-form-data'correctimport { FormDataRequest } from 'nestjs-form-data' - MemoryStoredFile
import { MemoryStoredFile } from 'nestjs-form-data' - IsFile, MaxFileSize, HasMimeType wrong
import { IsFile } from 'class-validator'correctimport { IsFile, MaxFileSize, HasMimeType } from 'nestjs-form-data'
Quickstart
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();