or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

application-interface.mdexpress-adapter.mdfile-upload.mdindex.md

file-upload.mddocs/

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

```