Search code examples
javascriptnode.jstypescriptnestjscloudinary

nest.js and cloudinary: problem with single image upload


I am trying to upload user's avatar to cloudinary, so far the function looks like this:

uploadStreamFile(file: Express.Multer.File): Promise<CloudinaryResponse> {
    return new Promise<CloudinaryResponse>((resolve, reject) => {
      const uploadStream = cloudinary.uploader.upload_stream(
        (error, result) => {
          if (error) return reject(error);
          resolve(result);
        },
      );

      streamifier.createReadStream(file.buffer).pipe(uploadStream);
    });
  }

It works, but my goal is to save the image and overwrite it if user will change avatar again. In cloudinary docs I found upload function that can take my desired options, so I modified function to:

  async uploadFile(file: Express.Multer.File, userId: string) {
    console.log(`FILE: ${file}`)
    console.log(`FILE NAME ${file.filename}`)
    console.log(`ORG NAME ${file.originalname}`)
    console.log(`FIELD NAME ${file.fieldname}`)

    const uploadResponse = await cloudinary.uploader.upload(file.originalname, {
    folder: 'avatars',
    public_id: userId,
    overwrite: true, 
  });

and the output:

649439028425fb78cabf8601
FILE: [object Object]
FILE NAME undefined
ORG NAME 103_gargamel.jpg
FIELD NAME file
[Nest] 62390  - 07/01/2023, 9:33:00 PM   ERROR [ExceptionsHandler] Object:
{
  "error": {
    "errno": -2,
    "code": "ENOENT",
    "syscall": "open",
    "path": "103_gargamel.jpg"
  }
}

I think request from frontend is ok, because file and userId are passed successfully. As first argument I tried to pass "avatar" (instead of file.originalname) or even binary buffer, but TS is expecting string there.

In case someone needs, this is the controller:

 @Post('upload/:userId')
    @UseInterceptors(FileInterceptor('file'))
    uploadImage(@UploadedFile() file: Express.Multer.File, @Param('userId') userId: string)
    {
      return this.cloudinaryService.uploadFile(file, userId);
    }

What am I missing?


Solution

  • If your Multer setup is correct(as it seems), you can do the following to convert incoming image to base64 and then send it as data URI, as it's suggesting in the docs(...a base64 data URI...):

      async uploadFile(file: Express.Multer.File, userId: string) {
        const imageAsBase64 = file.buffer.toString('base64');
        const dataURI = `data:text/plain;base64,${imageAsBase64}`;
    
        const uploadResponse = await cloudinary.uploader.upload(dataURI, {
        folder: 'avatars',
        public_id: userId,
        overwrite: true, 
      });
    

    I'd also suggest looking at this article and using streams instead:

    async uploadFile(
      file: Express.Multer.File,
    ): Promise<UploadApiResponse | UploadApiErrorResponse> {
      return new Promise((resolve, reject) => {
        v2.uploader.upload_stream(
          {
            resource_type: 'auto',
          },
          (error, result) => {
            if (error) return reject(error);
            resolve(result);
          }
        ).end(file.buffer)
      })
    }