Express.js HTTP platform adapter for NestJS applications with comprehensive file upload capabilities
—
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Pending
The risk profile of this skill
Comprehensive file upload functionality using Multer with interceptors for various upload scenarios, module configuration, validation, and error handling.
Interceptor functions that create NestJS interceptors for handling different file upload patterns.
/**
* Creates interceptor for single file upload handling
* @param fieldName - Form field name for the file input
* @param localOptions - Optional multer configuration for this interceptor
* @returns NestJS interceptor class for single file uploads
*/
function FileInterceptor(
fieldName: string,
localOptions?: MulterOptions
): Type<NestInterceptor>;Usage Examples:
import { FileInterceptor } from '@nestjs/platform-express';
import { Controller, Post, UseInterceptors, UploadedFile } from '@nestjs/common';
@Controller('upload')
export class UploadController {
@Post('single')
@UseInterceptors(FileInterceptor('file'))
uploadSingle(@UploadedFile() file: Express.Multer.File) {
return {
filename: file.filename,
size: file.size,
mimetype: file.mimetype
};
}
@Post('avatar')
@UseInterceptors(FileInterceptor('avatar', {
limits: { fileSize: 1024 * 1024 * 2 }, // 2MB limit
fileFilter: (req, file, callback) => {
if (file.mimetype.startsWith('image/')) {
callback(null, true);
} else {
callback(new Error('Only image files allowed'), false);
}
}
}))
uploadAvatar(@UploadedFile() file: Express.Multer.File) {
return { message: 'Avatar uploaded successfully' };
}
}/**
* Creates interceptor for multiple files upload from single field
* @param fieldName - Form field name for the file inputs
* @param maxCount - Maximum number of files to accept
* @param localOptions - Optional multer configuration for this interceptor
* @returns NestJS interceptor class for multiple file uploads
*/
function FilesInterceptor(
fieldName: string,
maxCount?: number,
localOptions?: MulterOptions
): Type<NestInterceptor>;Usage Examples:
@Controller('upload')
export class UploadController {
@Post('multiple')
@UseInterceptors(FilesInterceptor('files', 5))
uploadMultiple(@UploadedFiles() files: Express.Multer.File[]) {
return {
count: files.length,
files: files.map(file => ({
filename: file.filename,
size: file.size
}))
};
}
@Post('gallery')
@UseInterceptors(FilesInterceptor('images', 10, {
limits: { fileSize: 1024 * 1024 * 5 }, // 5MB per file
fileFilter: (req, file, callback) => {
const allowedTypes = ['image/jpeg', 'image/png', 'image/gif'];
if (allowedTypes.includes(file.mimetype)) {
callback(null, true);
} else {
callback(new Error('Invalid file type for gallery'), false);
}
}
}))
uploadGallery(@UploadedFiles() files: Express.Multer.File[]) {
return { message: `${files.length} images uploaded to gallery` };
}
}/**
* Creates interceptor for multiple files upload from multiple fields
* @param uploadFields - Array of field configurations with names and max counts
* @param localOptions - Optional multer configuration for this interceptor
* @returns NestJS interceptor class for file fields uploads
*/
function FileFieldsInterceptor(
uploadFields: MulterField[],
localOptions?: MulterOptions
): Type<NestInterceptor>;Usage Examples:
@Controller('upload')
export class UploadController {
@Post('profile')
@UseInterceptors(FileFieldsInterceptor([
{ name: 'avatar', maxCount: 1 },
{ name: 'background', maxCount: 1 },
{ name: 'documents', maxCount: 5 }
]))
uploadProfile(@UploadedFiles() files: {
avatar?: Express.Multer.File[],
background?: Express.Multer.File[],
documents?: Express.Multer.File[]
}) {
return {
avatar: files.avatar?.[0]?.filename,
background: files.background?.[0]?.filename,
documents: files.documents?.length || 0
};
}
@Post('mixed')
@UseInterceptors(FileFieldsInterceptor([
{ name: 'logo', maxCount: 1 },
{ name: 'images', maxCount: 10 },
{ name: 'videos', maxCount: 3 }
], {
limits: {
fileSize: 1024 * 1024 * 50, // 50MB max per file
files: 14 // Total file limit
}
}))
uploadMixed(@UploadedFiles() files: Record<string, Express.Multer.File[]>) {
return Object.keys(files).reduce((acc, key) => {
acc[key] = files[key].length;
return acc;
}, {} as Record<string, number>);
}
}/**
* Creates interceptor that accepts any files uploaded
* @param localOptions - Optional multer configuration for this interceptor
* @returns NestJS interceptor class for any file uploads
*/
function AnyFilesInterceptor(
localOptions?: MulterOptions
): Type<NestInterceptor>;/**
* Creates interceptor that accepts only text fields, no file uploads
* @param localOptions - Optional multer configuration for this interceptor
* @returns NestJS interceptor class that rejects file uploads
*/
function NoFilesInterceptor(
localOptions?: MulterOptions
): Type<NestInterceptor>;Usage Examples:
@Controller('upload')
export class UploadController {
@Post('any')
@UseInterceptors(AnyFilesInterceptor())
uploadAny(@UploadedFiles() files: Express.Multer.File[]) {
return { count: files.length };
}
@Post('text-only')
@UseInterceptors(NoFilesInterceptor())
textOnly(@Body() data: any) {
// Only text fields will be parsed, file uploads will be rejected
return { data };
}
}Dynamic module for configuring Multer globally across the application.
/**
* Dynamic module for Multer file upload configuration
* Provides global configuration for file upload behavior
*/
class MulterModule {
/**
* Register multer module with synchronous configuration
* @param options - Multer configuration options
* @returns Dynamic module for dependency injection
*/
static register(options?: MulterModuleOptions): DynamicModule;
/**
* Register multer module with asynchronous configuration
* @param options - Async configuration options with factory/class/existing patterns
* @returns Dynamic module for dependency injection
*/
static registerAsync(options: MulterModuleAsyncOptions): DynamicModule;
}Usage Examples:
// Basic module registration
@Module({
imports: [
MulterModule.register({
dest: './uploads',
limits: {
fileSize: 1024 * 1024 * 10 // 10MB
}
})
]
})
export class AppModule {}
// Async configuration with factory
@Module({
imports: [
MulterModule.registerAsync({
useFactory: async (configService: ConfigService) => ({
dest: configService.get('UPLOAD_PATH'),
limits: {
fileSize: configService.get('MAX_FILE_SIZE')
}
}),
inject: [ConfigService]
})
]
})
export class AppModule {}
// Async configuration with class
@Injectable()
export class MulterConfigService implements MulterOptionsFactory {
createMulterOptions(): MulterModuleOptions {
return {
storage: multer.diskStorage({
destination: './uploads',
filename: (req, file, cb) => {
const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1E9);
cb(null, file.fieldname + '-' + uniqueSuffix + path.extname(file.originalname));
}
})
};
}
}
@Module({
imports: [
MulterModule.registerAsync({
useClass: MulterConfigService
})
]
})
export class AppModule {}Type definitions for Multer configuration and file upload handling.
/**
* Configuration options for Multer file uploads
* Controls storage, limits, filtering, and processing behavior
*/
interface MulterOptions {
/** Destination directory for uploaded files */
dest?: string;
/** Custom storage engine configuration */
storage?: any;
/** File size and count limits */
limits?: {
/** Max field name size in bytes */
fieldNameSize?: number;
/** Max field value size in bytes */
fieldSize?: number;
/** Max number of non-file fields */
fields?: number;
/** Max file size in bytes */
fileSize?: number;
/** Max number of file fields */
files?: number;
/** Max number of parts (fields + files) */
parts?: number;
/** Max number of header key-value pairs */
headerPairs?: number;
};
/** Preserve file path information */
preservePath?: boolean;
/** File filtering function */
fileFilter?: (
req: any,
file: any,
callback: (error: Error | null, acceptFile: boolean) => void
) => void;
}
/**
* Field configuration for multi-field file uploads
* Defines field name and maximum file count per field
*/
interface MulterField {
/** Form field name */
name: string;
/** Maximum number of files for this field */
maxCount?: number;
}
/**
* Type alias for module-level Multer options
*/
type MulterModuleOptions = MulterOptions;
/**
* Factory interface for creating Multer options
* Used with async configuration patterns
*/
interface MulterOptionsFactory {
createMulterOptions(): Promise<MulterModuleOptions> | MulterModuleOptions;
}
/**
* Async configuration options for MulterModule
* Supports factory, class, and existing provider patterns
*/
interface MulterModuleAsyncOptions extends Pick<ModuleMetadata, 'imports'> {
/** Use existing provider */
useExisting?: Type<MulterOptionsFactory>;
/** Use class as provider */
useClass?: Type<MulterOptionsFactory>;
/** Use factory function */
useFactory?: (...args: any[]) => Promise<MulterModuleOptions> | MulterModuleOptions;
/** Dependencies to inject into factory */
inject?: any[];
}Constants used for dependency injection and error handling.
/** Injection token for Multer module options */
const MULTER_MODULE_OPTIONS: string = 'MULTER_MODULE_OPTIONS';Utility functions and constants for handling file upload errors.
/**
* Transform Multer and Busboy errors into appropriate NestJS HTTP exceptions
* @param error - Error object from Multer or Busboy with optional field information
* @returns Transformed error appropriate for HTTP responses
*/
function transformException(
error: (Error & { field?: string }) | undefined
): any;
/** Standard Multer error messages for various upload limits and validation failures */
const multerExceptions: {
LIMIT_PART_COUNT: string;
LIMIT_FILE_SIZE: string;
LIMIT_FILE_COUNT: string;
LIMIT_FIELD_KEY: string;
LIMIT_FIELD_VALUE: string;
LIMIT_FIELD_COUNT: string;
LIMIT_UNEXPECTED_FILE: string;
};
/** Standard Busboy error messages for multipart parsing failures */
const busboyExceptions: {
LIMIT_PART_COUNT: string;
LIMIT_FILE_SIZE: string;
LIMIT_FILE_COUNT: string;
LIMIT_FIELD_KEY: string;
LIMIT_FIELD_VALUE: string;
LIMIT_FIELD_COUNT: string;
LIMIT_UNEXPECTED_FILE: string;
MISSING_FIELD_NAME: string;
};Usage Examples:
// Custom error handling in file filter
const fileFilter = (req: any, file: any, callback: any) => {
if (!file.mimetype.startsWith('image/')) {
// This will be caught and transformed by transformException
return callback(new Error('Only image files are allowed'), false);
}
callback(null, true);
};
// Global exception filter for file upload errors
@Catch()
export class FileUploadExceptionFilter implements ExceptionFilter {
catch(exception: any, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const response = ctx.getResponse();
if (exception.code === 'LIMIT_FILE_SIZE') {
return response.status(413).json({
statusCode: 413,
message: 'File too large',
error: 'Payload Too Large'
});
}
// Handle other multer errors...
}
}Real-world configuration patterns for complex file upload scenarios.
// Custom storage configuration
import * as multer from 'multer';
import * as path from 'path';
const storage = multer.diskStorage({
destination: (req, file, cb) => {
const uploadPath = path.join('./uploads', req.user.id);
cb(null, uploadPath);
},
filename: (req, file, cb) => {
const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1E9);
const filename = file.fieldname + '-' + uniqueSuffix + path.extname(file.originalname);
cb(null, filename);
}
});
// Comprehensive file validation
const fileFilter = (req: any, file: any, callback: any) => {
const allowedTypes = {
'image/jpeg': ['.jpg', '.jpeg'],
'image/png': ['.png'],
'image/gif': ['.gif'],
'application/pdf': ['.pdf'],
'text/plain': ['.txt']
};
if (allowedTypes[file.mimetype]) {
const ext = path.extname(file.originalname).toLowerCase();
if (allowedTypes[file.mimetype].includes(ext)) {
callback(null, true);
} else {
callback(new Error('File extension does not match MIME type'), false);
}
} else {
callback(new Error('Unsupported file type'), false);
}
};
// Production-ready configuration
@Module({
imports: [
MulterModule.registerAsync({
useFactory: (configService: ConfigService) => ({
storage: storage,
fileFilter: fileFilter,
limits: {
fileSize: configService.get('MAX_FILE_SIZE', 1024 * 1024 * 10), // 10MB
files: configService.get('MAX_FILES', 5),
fields: configService.get('MAX_FIELDS', 10)
}
}),
inject: [ConfigService]
})
]
})
export class AppModule {}