0
# File Upload System
1
2
Comprehensive file upload functionality using Multer with interceptors for various upload scenarios, module configuration, validation, and error handling.
3
4
## Capabilities
5
6
### File Upload Interceptors
7
8
Interceptor functions that create NestJS interceptors for handling different file upload patterns.
9
10
#### Single File Upload
11
12
```typescript { .api }
13
/**
14
* Creates interceptor for single file upload handling
15
* @param fieldName - Form field name for the file input
16
* @param localOptions - Optional multer configuration for this interceptor
17
* @returns NestJS interceptor class for single file uploads
18
*/
19
function FileInterceptor(
20
fieldName: string,
21
localOptions?: MulterOptions
22
): Type<NestInterceptor>;
23
```
24
25
**Usage Examples:**
26
27
```typescript
28
import { FileInterceptor } from '@nestjs/platform-express';
29
import { Controller, Post, UseInterceptors, UploadedFile } from '@nestjs/common';
30
31
@Controller('upload')
32
export class UploadController {
33
@Post('single')
34
@UseInterceptors(FileInterceptor('file'))
35
uploadSingle(@UploadedFile() file: Express.Multer.File) {
36
return {
37
filename: file.filename,
38
size: file.size,
39
mimetype: file.mimetype
40
};
41
}
42
43
@Post('avatar')
44
@UseInterceptors(FileInterceptor('avatar', {
45
limits: { fileSize: 1024 * 1024 * 2 }, // 2MB limit
46
fileFilter: (req, file, callback) => {
47
if (file.mimetype.startsWith('image/')) {
48
callback(null, true);
49
} else {
50
callback(new Error('Only image files allowed'), false);
51
}
52
}
53
}))
54
uploadAvatar(@UploadedFile() file: Express.Multer.File) {
55
return { message: 'Avatar uploaded successfully' };
56
}
57
}
58
```
59
60
#### Multiple Files Upload
61
62
```typescript { .api }
63
/**
64
* Creates interceptor for multiple files upload from single field
65
* @param fieldName - Form field name for the file inputs
66
* @param maxCount - Maximum number of files to accept
67
* @param localOptions - Optional multer configuration for this interceptor
68
* @returns NestJS interceptor class for multiple file uploads
69
*/
70
function FilesInterceptor(
71
fieldName: string,
72
maxCount?: number,
73
localOptions?: MulterOptions
74
): Type<NestInterceptor>;
75
```
76
77
**Usage Examples:**
78
79
```typescript
80
@Controller('upload')
81
export class UploadController {
82
@Post('multiple')
83
@UseInterceptors(FilesInterceptor('files', 5))
84
uploadMultiple(@UploadedFiles() files: Express.Multer.File[]) {
85
return {
86
count: files.length,
87
files: files.map(file => ({
88
filename: file.filename,
89
size: file.size
90
}))
91
};
92
}
93
94
@Post('gallery')
95
@UseInterceptors(FilesInterceptor('images', 10, {
96
limits: { fileSize: 1024 * 1024 * 5 }, // 5MB per file
97
fileFilter: (req, file, callback) => {
98
const allowedTypes = ['image/jpeg', 'image/png', 'image/gif'];
99
if (allowedTypes.includes(file.mimetype)) {
100
callback(null, true);
101
} else {
102
callback(new Error('Invalid file type for gallery'), false);
103
}
104
}
105
}))
106
uploadGallery(@UploadedFiles() files: Express.Multer.File[]) {
107
return { message: `${files.length} images uploaded to gallery` };
108
}
109
}
110
```
111
112
#### File Fields Upload
113
114
```typescript { .api }
115
/**
116
* Creates interceptor for multiple files upload from multiple fields
117
* @param uploadFields - Array of field configurations with names and max counts
118
* @param localOptions - Optional multer configuration for this interceptor
119
* @returns NestJS interceptor class for file fields uploads
120
*/
121
function FileFieldsInterceptor(
122
uploadFields: MulterField[],
123
localOptions?: MulterOptions
124
): Type<NestInterceptor>;
125
```
126
127
**Usage Examples:**
128
129
```typescript
130
@Controller('upload')
131
export class UploadController {
132
@Post('profile')
133
@UseInterceptors(FileFieldsInterceptor([
134
{ name: 'avatar', maxCount: 1 },
135
{ name: 'background', maxCount: 1 },
136
{ name: 'documents', maxCount: 5 }
137
]))
138
uploadProfile(@UploadedFiles() files: {
139
avatar?: Express.Multer.File[],
140
background?: Express.Multer.File[],
141
documents?: Express.Multer.File[]
142
}) {
143
return {
144
avatar: files.avatar?.[0]?.filename,
145
background: files.background?.[0]?.filename,
146
documents: files.documents?.length || 0
147
};
148
}
149
150
@Post('mixed')
151
@UseInterceptors(FileFieldsInterceptor([
152
{ name: 'logo', maxCount: 1 },
153
{ name: 'images', maxCount: 10 },
154
{ name: 'videos', maxCount: 3 }
155
], {
156
limits: {
157
fileSize: 1024 * 1024 * 50, // 50MB max per file
158
files: 14 // Total file limit
159
}
160
}))
161
uploadMixed(@UploadedFiles() files: Record<string, Express.Multer.File[]>) {
162
return Object.keys(files).reduce((acc, key) => {
163
acc[key] = files[key].length;
164
return acc;
165
}, {} as Record<string, number>);
166
}
167
}
168
```
169
170
#### Any Files Upload
171
172
```typescript { .api }
173
/**
174
* Creates interceptor that accepts any files uploaded
175
* @param localOptions - Optional multer configuration for this interceptor
176
* @returns NestJS interceptor class for any file uploads
177
*/
178
function AnyFilesInterceptor(
179
localOptions?: MulterOptions
180
): Type<NestInterceptor>;
181
```
182
183
#### No Files Upload
184
185
```typescript { .api }
186
/**
187
* Creates interceptor that accepts only text fields, no file uploads
188
* @param localOptions - Optional multer configuration for this interceptor
189
* @returns NestJS interceptor class that rejects file uploads
190
*/
191
function NoFilesInterceptor(
192
localOptions?: MulterOptions
193
): Type<NestInterceptor>;
194
```
195
196
**Usage Examples:**
197
198
```typescript
199
@Controller('upload')
200
export class UploadController {
201
@Post('any')
202
@UseInterceptors(AnyFilesInterceptor())
203
uploadAny(@UploadedFiles() files: Express.Multer.File[]) {
204
return { count: files.length };
205
}
206
207
@Post('text-only')
208
@UseInterceptors(NoFilesInterceptor())
209
textOnly(@Body() data: any) {
210
// Only text fields will be parsed, file uploads will be rejected
211
return { data };
212
}
213
}
214
```
215
216
### Multer Module Configuration
217
218
Dynamic module for configuring Multer globally across the application.
219
220
```typescript { .api }
221
/**
222
* Dynamic module for Multer file upload configuration
223
* Provides global configuration for file upload behavior
224
*/
225
class MulterModule {
226
/**
227
* Register multer module with synchronous configuration
228
* @param options - Multer configuration options
229
* @returns Dynamic module for dependency injection
230
*/
231
static register(options?: MulterModuleOptions): DynamicModule;
232
233
/**
234
* Register multer module with asynchronous configuration
235
* @param options - Async configuration options with factory/class/existing patterns
236
* @returns Dynamic module for dependency injection
237
*/
238
static registerAsync(options: MulterModuleAsyncOptions): DynamicModule;
239
}
240
```
241
242
**Usage Examples:**
243
244
```typescript
245
// Basic module registration
246
@Module({
247
imports: [
248
MulterModule.register({
249
dest: './uploads',
250
limits: {
251
fileSize: 1024 * 1024 * 10 // 10MB
252
}
253
})
254
]
255
})
256
export class AppModule {}
257
258
// Async configuration with factory
259
@Module({
260
imports: [
261
MulterModule.registerAsync({
262
useFactory: async (configService: ConfigService) => ({
263
dest: configService.get('UPLOAD_PATH'),
264
limits: {
265
fileSize: configService.get('MAX_FILE_SIZE')
266
}
267
}),
268
inject: [ConfigService]
269
})
270
]
271
})
272
export class AppModule {}
273
274
// Async configuration with class
275
@Injectable()
276
export class MulterConfigService implements MulterOptionsFactory {
277
createMulterOptions(): MulterModuleOptions {
278
return {
279
storage: multer.diskStorage({
280
destination: './uploads',
281
filename: (req, file, cb) => {
282
const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1E9);
283
cb(null, file.fieldname + '-' + uniqueSuffix + path.extname(file.originalname));
284
}
285
})
286
};
287
}
288
}
289
290
@Module({
291
imports: [
292
MulterModule.registerAsync({
293
useClass: MulterConfigService
294
})
295
]
296
})
297
export class AppModule {}
298
```
299
300
### File Upload Interfaces
301
302
Type definitions for Multer configuration and file upload handling.
303
304
```typescript { .api }
305
/**
306
* Configuration options for Multer file uploads
307
* Controls storage, limits, filtering, and processing behavior
308
*/
309
interface MulterOptions {
310
/** Destination directory for uploaded files */
311
dest?: string;
312
313
/** Custom storage engine configuration */
314
storage?: any;
315
316
/** File size and count limits */
317
limits?: {
318
/** Max field name size in bytes */
319
fieldNameSize?: number;
320
/** Max field value size in bytes */
321
fieldSize?: number;
322
/** Max number of non-file fields */
323
fields?: number;
324
/** Max file size in bytes */
325
fileSize?: number;
326
/** Max number of file fields */
327
files?: number;
328
/** Max number of parts (fields + files) */
329
parts?: number;
330
/** Max number of header key-value pairs */
331
headerPairs?: number;
332
};
333
334
/** Preserve file path information */
335
preservePath?: boolean;
336
337
/** File filtering function */
338
fileFilter?: (
339
req: any,
340
file: any,
341
callback: (error: Error | null, acceptFile: boolean) => void
342
) => void;
343
}
344
345
/**
346
* Field configuration for multi-field file uploads
347
* Defines field name and maximum file count per field
348
*/
349
interface MulterField {
350
/** Form field name */
351
name: string;
352
/** Maximum number of files for this field */
353
maxCount?: number;
354
}
355
356
/**
357
* Type alias for module-level Multer options
358
*/
359
type MulterModuleOptions = MulterOptions;
360
361
/**
362
* Factory interface for creating Multer options
363
* Used with async configuration patterns
364
*/
365
interface MulterOptionsFactory {
366
createMulterOptions(): Promise<MulterModuleOptions> | MulterModuleOptions;
367
}
368
369
/**
370
* Async configuration options for MulterModule
371
* Supports factory, class, and existing provider patterns
372
*/
373
interface MulterModuleAsyncOptions extends Pick<ModuleMetadata, 'imports'> {
374
/** Use existing provider */
375
useExisting?: Type<MulterOptionsFactory>;
376
/** Use class as provider */
377
useClass?: Type<MulterOptionsFactory>;
378
/** Use factory function */
379
useFactory?: (...args: any[]) => Promise<MulterModuleOptions> | MulterModuleOptions;
380
/** Dependencies to inject into factory */
381
inject?: any[];
382
}
383
```
384
385
### File Upload Constants
386
387
Constants used for dependency injection and error handling.
388
389
```typescript { .api }
390
/** Injection token for Multer module options */
391
const MULTER_MODULE_OPTIONS: string = 'MULTER_MODULE_OPTIONS';
392
```
393
394
### Error Handling
395
396
Utility functions and constants for handling file upload errors.
397
398
```typescript { .api }
399
/**
400
* Transform Multer and Busboy errors into appropriate NestJS HTTP exceptions
401
* @param error - Error object from Multer or Busboy with optional field information
402
* @returns Transformed error appropriate for HTTP responses
403
*/
404
function transformException(
405
error: (Error & { field?: string }) | undefined
406
): any;
407
408
/** Standard Multer error messages for various upload limits and validation failures */
409
const multerExceptions: {
410
LIMIT_PART_COUNT: string;
411
LIMIT_FILE_SIZE: string;
412
LIMIT_FILE_COUNT: string;
413
LIMIT_FIELD_KEY: string;
414
LIMIT_FIELD_VALUE: string;
415
LIMIT_FIELD_COUNT: string;
416
LIMIT_UNEXPECTED_FILE: string;
417
};
418
419
/** Standard Busboy error messages for multipart parsing failures */
420
const busboyExceptions: {
421
LIMIT_PART_COUNT: string;
422
LIMIT_FILE_SIZE: string;
423
LIMIT_FILE_COUNT: string;
424
LIMIT_FIELD_KEY: string;
425
LIMIT_FIELD_VALUE: string;
426
LIMIT_FIELD_COUNT: string;
427
LIMIT_UNEXPECTED_FILE: string;
428
MISSING_FIELD_NAME: string;
429
};
430
```
431
432
**Usage Examples:**
433
434
```typescript
435
// Custom error handling in file filter
436
const fileFilter = (req: any, file: any, callback: any) => {
437
if (!file.mimetype.startsWith('image/')) {
438
// This will be caught and transformed by transformException
439
return callback(new Error('Only image files are allowed'), false);
440
}
441
callback(null, true);
442
};
443
444
// Global exception filter for file upload errors
445
@Catch()
446
export class FileUploadExceptionFilter implements ExceptionFilter {
447
catch(exception: any, host: ArgumentsHost) {
448
const ctx = host.switchToHttp();
449
const response = ctx.getResponse();
450
451
if (exception.code === 'LIMIT_FILE_SIZE') {
452
return response.status(413).json({
453
statusCode: 413,
454
message: 'File too large',
455
error: 'Payload Too Large'
456
});
457
}
458
459
// Handle other multer errors...
460
}
461
}
462
```
463
464
### Advanced Configuration Examples
465
466
Real-world configuration patterns for complex file upload scenarios.
467
468
```typescript
469
// Custom storage configuration
470
import * as multer from 'multer';
471
import * as path from 'path';
472
473
const storage = multer.diskStorage({
474
destination: (req, file, cb) => {
475
const uploadPath = path.join('./uploads', req.user.id);
476
cb(null, uploadPath);
477
},
478
filename: (req, file, cb) => {
479
const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1E9);
480
const filename = file.fieldname + '-' + uniqueSuffix + path.extname(file.originalname);
481
cb(null, filename);
482
}
483
});
484
485
// Comprehensive file validation
486
const fileFilter = (req: any, file: any, callback: any) => {
487
const allowedTypes = {
488
'image/jpeg': ['.jpg', '.jpeg'],
489
'image/png': ['.png'],
490
'image/gif': ['.gif'],
491
'application/pdf': ['.pdf'],
492
'text/plain': ['.txt']
493
};
494
495
if (allowedTypes[file.mimetype]) {
496
const ext = path.extname(file.originalname).toLowerCase();
497
if (allowedTypes[file.mimetype].includes(ext)) {
498
callback(null, true);
499
} else {
500
callback(new Error('File extension does not match MIME type'), false);
501
}
502
} else {
503
callback(new Error('Unsupported file type'), false);
504
}
505
};
506
507
// Production-ready configuration
508
@Module({
509
imports: [
510
MulterModule.registerAsync({
511
useFactory: (configService: ConfigService) => ({
512
storage: storage,
513
fileFilter: fileFilter,
514
limits: {
515
fileSize: configService.get('MAX_FILE_SIZE', 1024 * 1024 * 10), // 10MB
516
files: configService.get('MAX_FILES', 5),
517
fields: configService.get('MAX_FIELDS', 10)
518
}
519
}),
520
inject: [ConfigService]
521
})
522
]
523
})
524
export class AppModule {}
525
```