instantcommerce / next-api-decorators

Collection of decorators to create typed Next.js API routes, with easy request validation and transformation.
https://next-api-decorators.vercel.app
MIT License
412 stars 29 forks source link

UploadedFile decorator not working with multipart/form-data #510

Closed kodama007 closed 1 year ago

kodama007 commented 1 year ago

I declared a class handler with a single endpoint that should accept a file, however whenever I send a request, the file argument is undefined. I'm sending a POST endpoint with multipart/form-data with a single key named file. I've removed all middlewares, just in case, but it's still not working.

class CasesRouter {
    @Post()
    @HttpCode(200)
    async register(@UploadedFile() file: any) {
        console.log(file)
        return 'test'
    }
}

export default createHandler(CasesRouter);

export const config = {
    api: {
        bodyParser: false,
    },
};
ggurkal commented 1 year ago

Are you using multer or any other middleware? If not, you have to use one in order to make use of the UploadedFile decorator.

kodama007 commented 1 year ago

I installed multer and set it up, and it now works. Thanks. I think a basic setup for file uploads should be added to the docs though.

Updated code:

import multer from 'multer'

const upload = multer({
    dest: "uploads/",
    limits: {
        fileSize: 52428800,
    },
}).single('file')

class CasesRouter {
    @Post()
    @UseMiddleware(upload)
    async register(@UploadedFile() file: any) {
        console.log(file)
        return 'test'
    }
}

export default createHandler(CasesRouter);

export const config = {
    api: {
        bodyParser: false,
    },
};
kodama007 commented 1 year ago

Do the class-validators classes work with the UploadedFile decorator? I created a class and a custom validator to validate the image mime type, but for some reason it's not working as expected.

export class FileUploadDTO {
    @IsFile(['image/jpg'], { message: 'Not a valid image' })
    file: any;
}
export const IsFile = <T>(
    mimes: string[],
    validationOptions?: ValidationOptions,
) => {
  return (object: any, propertyName: string) => {
    registerDecorator({
      target: object.constructor,
      propertyName,
      options: validationOptions,
      constraints: [mimes],
      validator: FileConstraint,
    });
  };
};

@ValidatorConstraint({ name: "IsFile" })
export class FileConstraint implements ValidatorConstraintInterface {
  validate(value: any, args: ValidationArguments) {
      const [mimes] = args.constraints;
      console.log(!!(value?.mimetype && (mimes ?? []).includes(value?.mimetype)))
      return !!(value?.mimetype && (mimes ?? []).includes(value?.mimetype));
  }
}
    async register(@UploadedFile() file: FileUploadDTO) {
        return 'tests'
    }
ggurkal commented 1 year ago

The built-in validation pipe works only on req.body, however uploaded files are held in req.file or req.files. So I don't think the validation will work as you expect it to work.