#Multer upload files optional

19 messages · Page 1 of 1 (latest)

zenith anvil
#

Hello guys i want to upload two files one is requried and the other is optional
idk if thats possible any helpful resource or help will be thankful

  @UseInterceptors(
    FileFieldsInterceptor([
      { name: 'logoURL', maxCount: 1 },
      { name: 'backgroundURL', maxCount: 1 },
    ]),
  )
  async create(
    @Req() req: IAuthRequest,
    @Body() dto: CreateStoreDto,
    @UploadedFiles(FilesValidationPipe)
    files: {
      logoURL: Express.Multer.File;
      backgroundURL?: Express.Multer.File;
    },
  ): Promise<StoreResponse> {
    console.log(files);
  }

I want the fileValidationPipe to validate that !

@Injectable()
export class FilesValidationPipe implements PipeTransform {
  private readonly allowedMimeTypes = ['image/jpeg', 'image/png', 'image/webp'];
  private readonly maxMB = 5; // 5MB
  private readonly maxFileSize = this.maxMB * 1024 * 1024;

  transform(files: FilesParameter): Express.Multer.File[] {
    const validFiles: Express.Multer.File[] = [];

    for (const key in files) {
      const file = files[key][0];

      if (!this.isMimeTypeValid(file.mimetype)) {
        console.log(file);
        throw new BadRequestException(
          'Invalid file type. Only JPEG, PNG, and WebP images are allowed.',
        );
      }

      if (file.size > this.maxFileSize) {
        throw new BadRequestException(
          `File size exceeds the allowed limit. Max allowed size is ${this.maxMB}MB.`,
        );
      }

      validFiles.push(file);
    }

    return validFiles;
  }

  private isMimeTypeValid(mimeType: string): boolean {
    return this.allowedMimeTypes.includes(mimeType);
  }
}

BTW I am using multer the right way ?
I mean i want to upload one image per one am i using the right methods ?

mortal geode
#

yea a custom validation pipe is the only correct answer, unless you want to use Multer for validation but I wouldn't recommend it.

you should receive

backgroundURL: [
{
  fieldname: string;
  originalname: string;
  encoding: string;
  mimetype: string;
  size: number;
  stream: Readable;
  destination: string;
  filename: string;
  path: string;
  buffer: Buffer;
}
],
logoURL: [
{
  fieldname: string;
  originalname: string;
  encoding: string;
  mimetype: string;
  size: number;
  stream: Readable;
  destination: string;
  filename: string;
  path: string;
  buffer: Buffer;
}
]

so give this an interface, and this will be the files u receive in the pipe params.

and extract each one on it's own

const {logoURL, backgroundURL} = files

and then u can check for the required one and the opt one

Hope that answers your question.

zenith anvil
#

Okay but my issue was to validate optional and required files using ZOd

zenith anvil
mortal geode
mortal geode
zenith anvil
#

I am sorry for that but things got complicated in front of me due to that

zenith anvil
mortal geode
#

maybe I didn't make it clear, what I meant is that you can do the following.
Assuming logoURL is the required file.

if(!logoURL[0] || Object.keys(logoURL[0])?.length > 0) {
throw new HttpException("logo is required", HttpStatus.NOT_ACCEPTABLE)
}

and for backgroundURL you can simply not check for it.

also wrap your code with a try catch

zenith anvil
#

But I appreciate your method also !

mortal geode
#

the Dto only validates req.body, and here you're dealing with multi-part forms. and these files are in req.file/files

zenith anvil
#

I used Zod form data package thought that gonna help

mortal geode
#

You can try to extract the raw body and validate it using JOI, if that's what you are looking for.

But that doesn't mean it is better, this separation makes your code scalable and readable.

zenith anvil
#

That what i have reached lately
let me know what do you think !

  async create(
    @Req() req: IAuthRequest,
    @Body() dto: CreateStoreDto,
    @UploadedFiles(
      new FileValidationPipe([
        { name: 'logoURL', isRequired: true },
        { name: 'backgroundURL', isRequired: false },
      ]),
    )
    files: {
      logoURL: Express.Multer.File;
      background?: Express.Multer.File;
    },
  ): Promise<StoreResponse> {
  // etc..    
}
@Injectable()
export class FileValidationPipe implements PipeTransform {
  constructor(
    private readonly fields: { name: string; isRequired: boolean }[],
  ) {}
  private readonly allowedMimeTypes = ['image/jpeg', 'image/png', 'image/webp'];
  private readonly maxMB = 5; // 5MB
  private readonly maxFileSize = this.maxMB * 1024 * 1024;

  transform(files: FileData) {
    for (const field of this.fields) {
      if (field.isRequired && !files[field.name]) {
        throw new BadRequestException(`Field ${field.name} is required.`);
      }
    }

    const validFiles = [];

    for (const key in files) {
      const fileArray = files[key];

      if (Array.isArray(fileArray)) {
        for (const file of fileArray) {
          if (!this.isMimeTypeValid(file.mimetype)) {
            throw new BadRequestException(
              'Invalid file type. Only JPEG, PNG, and WebP images are allowed.',
            );
          }

          if (file.size > this.maxFileSize) {
            throw new BadRequestException(
              `File size exceeds the allowed limit. Max allowed size is ${this.maxMB}MB.`,
            );
          }

          validFiles.push(file);
        }
      } else {
        throw new BadRequestException(
          'Invalid file structure. Expected an array of files.',
        );
      }
    }

    return validFiles;
  }

  private isMimeTypeValid(mimeType: string): boolean {
    return this.allowedMimeTypes.includes(mimeType);
  }
}
mortal geode
#

You can message me because if u want all my notes this won't be the proper place

#

I can give u a playlist i'm making for clean code

zenith anvil
#

OK I will