Mastering File Upload and Validation in NestJS with Multer
Published: 2023-04-28 12:28:38

---
title: Mastering File Upload and Validation in NestJS with Multer
published: true
date: 2023-03-18 12:04:39 UTC
tags: 
canonical_url: https://thecodeway.hashnode.dev/mastering-file-upload-and-validation-in-nestjs-with-multer
---

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1679054576314/bb731ed7-bf84-4398-a48e-95e226440017.jpeg?w=1600&h=840&fit=crop&crop=entropy&auto=compress,format&format=webp)

This guide will show you how to efficiently incorporate file upload and validation into your NestJS project. The tutorial will walk you through the steps of receiving and verifying files using the Multer middleware package with the Express Adapter. By following this guide, you can create a personalized file upload and validation process that works best for your NestJS application.

## Accepting files

File processing in NestJs is handled with Multer. It has a built-in Multer middleware package for Express Adapter. The only required package to install is `@types/multer`, which is for better type safety.

```
npm i -D @types/multer @nestjs/platform-express

```

Start by creating an endpoint for single file uploads.

```
import { Post, UploadedFile, UseInterceptors, Controller } from '@nestjs/common';
import { FileInterceptor } from '@nestjs/platform-express';

@Controller('files')
export class FileController {
    @Post('upload')
    @UseInterceptors(FileInterceptor('file'))
    uploadFile(@UploadedFile() file: Express.Multer.File) {
        console.log(file);
    }
}

```

> Make sure to use `@nestjs/common: "^9.0.0"`, it's required to use `UploadedFile`.

If you use swagger, you can specify `ApiConsumes` decorator to `multipart/form-data`. This tells swagger that this route accepts form data.

```
import { Post, UploadedFile, UseInterceptors, Controller } from '@nestjs/common';
import { FileInterceptor } from '@nestjs/platform-express';
import { ApiConsumes, ApiTags } from '@nestjs/swagger';

@ApiTags('Uploding Files')
@Controller('files')
@ApiConsumes('multipart/form-data')
export class FileController {
    @Post('upload')
    @UseInterceptors(FileInterceptor('file'))
    uploadFile(@UploadedFile() file: Express.Multer.File) {
        console.log(file);
    }
}

```

When it comes to filing storage, Multer has a default memory setting. While this is a viable choice, it may not suit your needs if you plan to bypass disk storage and instead upload files directly to external storage solutions like Cloudinary or AWS S3.

### How to Save Data to Disk Storage

To upload your files to disk storage, you can provide the file destination in the `FileInterceptor`. The interceptor accepts [Multer Options](https://github.com/expressjs/multer#multeropts).

```
const UPLOAD_DIR = './upload/files/';

@ApiTags('Uploding Files')
@Controller('files')
@ApiConsumes('multipart/form-data')
export class FileController {
    @Post('upload')
    @UseInterceptors(FileInterceptor('file', { dest: UPLOAD_DIR }))
    uploadFile(@UploadedFile() file: Express.Multer.File) {
        console.log(file);
    }
}

```

The upload directory path is relative to your root directory.

Hey there! When dealing with a lot of users, we want to make sure we avoid any issues that might come up with file names. To prevent this from happening, we recommend assigning a separate folder for each upload. That way, everything stays organized and everyone can easily find what they need.

There is a helpful tip to share with you. Are you aware that every user can have their unique folder in the upload directory? What's more, you can enhance this feature by configuring a storage engine for Multer, which allows you to create a dynamic upload path. Why not give it a try?

```
import { Post, UploadedFile, UseInterceptors, Controller } from '@nestjs/common';
import { FileInterceptor } from '@nestjs/platform-express';
import { ApiConsumes, ApiTags } from '@nestjs/swagger';
import { Request } from 'express';
import { diskStorage } from 'multer';
import * as path from 'path';

const UPLOAD_DIR = './upload/files/';

// User interface of the authenticated user
interface User {
  id: string;
}

/**
 * You can use this function to generate a unique filename for each file
 * User id is used to generate a unique filename
 * The User object can be attached to the request object in the auth middleware
 */
const defaultConfig = diskStorage({
  destination: UPLOAD_DIR,
  filename: (req: Request & { user: User }, file, cb) => {
    const uid = req.user.id;
    cb(null, `${uid}${path.extname(file.originalname)}`)
  }
})

@ApiTags('Uploding Files')
@Controller('files')
@ApiConsumes('multipart/form-data')
export class FileController {

  @Post('upload')
  @UseInterceptors(FileInterceptor('file', { storage: defaultConfig }))
  uploadFile(@UploadedFile() file: Express.Multer.File) {
    console.log(file);
  }

}

```

In this example, the user object could be attached to the request manually in the auth [middleware](https://docs.nestjs.com/middleware).

This is a perfect example of how to accept files and save them in a super cool and unique path!

> The file would be saved to disk before `console.log(file)` runs.

The next section provides a way of adding file processing like validation of files uploaded.

## Validate Your Files with ParseFilePipe and ParseFilePipeBuilder

### Using ParseFilePipe with Validator classes

NestJs Pipes offer an efficient way to validate files. To perform file validation, simply create a validator class such as MaxFileSizeValidator or FileNameValidator, and pass it to the ParseFilePipe.

```
import { ..., ParseFilePipe } from '@nestjs/common';

...

@Post('upload')
@UseInterceptors(FileInterceptor('file', { storage: defaultConfig }))
uploadFile(
  @UploadedFile(
    new ParseFilePipe({
      validators: [
        // ... Set of file validator instances here
      ]
    })
  )
  file: Express.Multer.File
) {
  console.log(file);
}

...

```

Create a `FileValidator` class to use with `ParseFilePipe`.

```
import { FileValidator } from '@nestjs/common';

class MaxFileSize extends FileValidator<{ maxSize: number }>{
  constructor(options: { maxSize: number }) {
    super(options)
  }

  isValid(file: Express.Multer.File): boolean | Promise<boolean> {
    const in_mb = file.size / 1000000
    return in_mb <= this.validationOptions.maxSize
  }
  buildErrorMessage(): string {
    return `File uploaded is too big. Max size is (${this.validationOptions.maxSize} MB)`
  }
}

```

The interface`FileValidator` has two methods needed to create its class. The method `isValid` and `buildErrorMessage`.

The method, `isValid` will contain your logic that defines if the file is valid or not depending on the options passed in the constructor.

From the constructor definition, `maxSize` can be passed as an option and is used in the `isValid` method to know what is considered the maximum file size.

```
/**
 * Builds an error message in case the validation fails.
 * @param file the file from the request object
 */
abstract buildErrorMessage(file: any): string;

```

Above is the interface of the `buildErrorMessage` function. It's used to build unique or general error messages for uploaded files.

Pass an instance of the `MaxFileSize` class in the `ParseFilePipe`. The `maxSize` can be passed to the constructor during instantiation.

```
...

const MAX_UPLOAD_SIZE = 10; // in MB
...

@Post('upload')
@UseInterceptors(FileInterceptor('file', { storage: defaultConfig }))
uploadFile(
  @UploadedFile(
    new ParseFilePipe({
      validators: [
        // ... Set of file validator instances here
        new MaxFileSize({
          maxSize: MAX_UPLOAD_SIZE
        }),
      ]
    })
  )
  file: Express.Multer.File
) {
  console.log(file);
}

...

```

The code should look like this now

```
import { Post, UploadedFile, UseInterceptors, Controller, ParseFilePipe } from '@nestjs/common';
import { FileInterceptor } from '@nestjs/platform-express';
import { ApiConsumes, ApiTags } from '@nestjs/swagger';
import { Request } from 'express';
import { diskStorage } from 'multer';
import * as path from 'path';
import { FileValidator } from '@nestjs/common';

const UPLOAD_DIR = './upload/files/';
const MAX_UPLOAD_SIZE = 10; // in MB

// User interface of the authenticated user
interface User {
  id: string;
}

class MaxFileSize extends FileValidator<{ maxSize: number }>{
  constructor(options: { maxSize: number }) {
    super(options)
  }

  isValid(file: Express.Multer.File): boolean | Promise<boolean> {
    const in_mb = file.size / 1000000
    return in_mb <= this.validationOptions.maxSize
  }
  buildErrorMessage(): string {
    return `File uploaded is too big. Max size is (${this.validationOptions.maxSize} MB)`
  }
}

/**
 * You can use this function to generate a unique filename for each file
 * User id is used to generate a unique filename
 * The User object can be attached to the request object in the auth middleware
 */
const defaultConfig = diskStorage({
  destination: UPLOAD_DIR,
  filename: (req: Request & { user: User }, file, cb) => {
    const uid = req.user.id;
    cb(null, `${uid}${path.extname(file.originalname)}`)
  }
})

@ApiTags('Uploding Files')
@Controller('files')
@ApiConsumes('multipart/form-data')
export class FileController {

  @Post('upload')
  @UseInterceptors(FileInterceptor('file', { storage: defaultConfig }))
  uploadFile(
    @UploadedFile(
      new ParseFilePipe({
        validators: [
          // ... Set of file validator instances here
          new MaxFileSize({
            maxSize: MAX_UPLOAD_SIZE
          }),
        ]
      })
    )
    file: Express.Multer.File
  ) {
    console.log(file);
  }

}

```

> If your validators are getting much, you can create them in a separate file and import them here as a named constant like `myValidators.` - [NestJs Docs](https://docs.nestjs.com/techniques/file-upload#:~:text=.File%2C-,HINT,-If%20the%20number)

### Using ParseFilePipeBuilder

Wow, this is amazing! It's so much better because it comes with built-in validators for file size and type! Plus, there's a way to add even more validators using the addValidator method for complex file validation! How cool is that?!

```
import { ..., ParseFilePipeBuilder, HttpStatus } from '@nestjs/common';
...

@Post('upload')
@UseInterceptors(FileInterceptor('file', { storage: defaultConfig }))
uploadFile(
  @UploadedFile(
    new ParseFilePipeBuilder()
      .addFileTypeValidator({
        fileType: /(jpg|jpeg|png|gif)$/,
      })
      .addMaxSizeValidator({
        maxSize: 1000
      })
      .addValidator(
        new MaxFileSize({
          maxSize: MAX_UPLOAD_SIZE
        }),
      ) 
      .build({
        errorHttpStatusCode: HttpStatus.UNPROCESSABLE_ENTITY
      })
  )
  file: Express.Multer.File
) {
  console.log(file);
}

...

```

`errorHttpStatusCode` is the HTTP status code you want to be returned when validation fails.

Simplify the process of file validation with the help of ParseFilePipeBuilder. For straightforward validations like file types and sizes, this tool is highly recommended. However, for more intricate validations, it's best to create file validator classes.

## Optional File Uploads

File upload parameters can be made optional or not required by passing an extra option in the `ParseFilePipeBuilder` build method.

```
new ParseFilePipeBuilder()
.addFileTypeValidator({
  fileType: "png",
})
.build({
  fileIsRequired: false // It's required by default
})

```

## Accepting multiple files

Simply change the file interceptor and upload the file decorator.

```
import { UploadedFiles } from '@nestjs/common';
import { FilesInterceptor } from '@nestjs/platform-express';

const MAX_FILES_COUNT = 10;

...

@Post('upload/multiple')
@UseInterceptors(
  FilesInterceptor('files', MAX_FILES_COUNT, { storage: defaultConfig })
)
uploadFiles(
  @UploadedFiles(
    new ParseFilePipeBuilder()
      .addFileTypeValidator({
        fileType: /(jpg|jpeg|png|gif)$/,
      })
      .addMaxSizeValidator({
        maxSize: 1000
      })
      .addValidator(
        new MaxFileSize({
          maxSize: MAX_UPLOAD_SIZE
        }),
      ) 
      .build({
        errorHttpStatusCode: HttpStatus.UNPROCESSABLE_ENTITY
      })
  )
  files: Express.Multer.File[]
) {
  console.log(files);
}

...

```

It's now `UploadedFiles` and `FilesInterceptor`. `FilesInterceptor` accepts max files number that can be uploaded through the endpoint.

> Make sure to accept files as an argument, not just file.

Below, you can find the complete code.

```
import { Post, UploadedFile, UseInterceptors, Controller, ParseFilePipe, ParseFilePipeBuilder, HttpStatus, UploadedFiles } from '@nestjs/common';
import { FileInterceptor, FilesInterceptor } from '@nestjs/platform-express';
import { ApiConsumes, ApiTags } from '@nestjs/swagger';
import { Request } from 'express';
import { diskStorage } from 'multer';
import * as path from 'path';
import { FileValidator } from '@nestjs/common';

const UPLOAD_DIR = './upload/files/';
const MAX_UPLOAD_SIZE = 10; // in MB
const MAX_FILES_COUNT = 10; // Maximum number of files that can be uploaded at once

// User interface of the authenticated user
interface User {
  id: string;
}

class MaxFileSize extends FileValidator<{ maxSize: number }>{
  constructor(options: { maxSize: number }) {
    super(options)
  }

  isValid(file: Express.Multer.File): boolean | Promise<boolean> {
    const in_mb = file.size / 1000000
    return in_mb <= this.validationOptions.maxSize
  }
  buildErrorMessage(): string {
    return `File uploaded is too big. Max size is (${this.validationOptions.maxSize} MB)`
  }
}

/**
 * You can use this function to generate a unique filename for each file
 * User id is used to generate a unique filename
 * The User object can be attached to the request object in the auth middleware
 */
const defaultConfig = diskStorage({
  destination: UPLOAD_DIR,
  filename: (req: Request & { user: User }, file, cb) => {
    const uid = req.user.id;
    cb(null, `${uid}${path.extname(file.originalname)}`)
  }
})

@ApiTags('Uploding Files')
@Controller('files')
@ApiConsumes('multipart/form-data')
export class FileController {

  @Post('upload')
  @UseInterceptors(FileInterceptor('file', { storage: defaultConfig }))
  uploadFile(
    @UploadedFile(
      new ParseFilePipeBuilder()
        .addFileTypeValidator({
          fileType: /(jpg|jpeg|png|gif)$/,
        })
        .addMaxSizeValidator({
          maxSize: 1000
        })
        .build({
          errorHttpStatusCode: HttpStatus.UNPROCESSABLE_ENTITY
        })
    )
    file: Express.Multer.File
  ) {
    console.log(file);
  }

  @Post('upload/multiple')
  @UseInterceptors(
    FilesInterceptor('files', MAX_FILES_COUNT, { storage: defaultConfig })
  )
  uploadFiles(
    @UploadedFiles(
      new ParseFilePipeBuilder()
        .addFileTypeValidator({
          fileType: /(jpg|jpeg|png|gif)$/,
        })
        .addValidator(
          new MaxFileSize({
            maxSize: MAX_UPLOAD_SIZE
          }),
        )
        .build({
          errorHttpStatusCode: HttpStatus.UNPROCESSABLE_ENTITY
        })
    )
    files: Express.Multer.File[]
  ) {
    console.log(files);
  }
}

```

For a comprehensive configuration of Multer file upload, refer to the NestJs File Upload [Documentation](https://docs.nestjs.com/techniques/file-upload). Read on to learn more.

## Conclusion

In conclusion, this article provides a comprehensive guide on how to implement file upload and validation in NestJS using the Multer middleware package for the Express Adapter. It covers accepting and validating files, saving files to disk storage, file validation with ParseFilePipe and ParseFilePipeBuilder, and accepting multiple files. By following the steps outlined in this article, developers can easily design their custom file upload and validation flow in NestJS.