node.jsimagevue.jsexpresssendfile

Vuejs not loading image completely from backend API


Context: I've got a WebApp (frontend: Vue3, backend: node.js + express) with a single vue, and a button. When clicking on a button it triggers a script in the backend, downloading an image, and storing it locally. This image should be rendered on the Vue side.

My Issue: Everything seems to work, except that Vue is not displaying the image completely. I only have a small share of the file displayed.

Backend Controller (images.controller.js):

const fs = require('fs');
const path = require('path');
const axios = require('axios');
const { v4: uuidv4 } = require('uuid');

const downloadImage = async(fileUrl, fileName) => {
    let isDownloaded = false
    const downloadFolder = './images';

    const localFilePath = path.resolve(__dirname, downloadFolder, fileName);

    try {
        const response = await axios({
            method: 'GET',
            url: fileUrl,
            responseType: 'stream',
        });

        if(response.status === 200){
            isDownloaded = true;
            await response.data.pipe(fs.createWriteStream(localFilePath));
        }


    } catch (error) {
        console.log(error)
    }

    return isDownloaded;
}

const readImage = async(req, res) => {
    try {
        const fileName = `${uuidv4()}.jpeg`;
        const fileUrl = 'https://hips.hearstapps.com/hmg-prod.s3.amazonaws.com/images/dog-puppy-on-garden-royalty-free-image-1586966191.jpg';
        const isDownloaded = await downloadImage(fileUrl, fileName);

        if(isDownloaded){
            const img = fs.readFile(__dirname + '/images/' + fileName, async(err, data) => {
                var contenType = 'image/jpeg';
                var base64 = await Buffer.from(data).toString('base64');
                base64 = 'data:image/jpeg;base64,' + base64;
                res.status(200).send(base64)
            })
        }
    } catch (error) {
        console.log(error)
    }
}

module.exports = {
    readImage: readImage,
}

Frontend side, my Home.vue:

<template>
  <div class="home">
    <button @click="readImage">TEST</button>
    <div v-if="img">
      <img :src="img">
    </div>
  </div>
</template>

<script>
import ImagesService from '../services/images.service'
export default {
  name: 'Home',
  data(){
    return {
      img: ''
    }
  },

  methods: {
    async readImage(){
      const temp = await ImagesService.readImages({name: 'dog.jpeg'})
      this.img = temp.data
    }
  },
}
</script>

images.service.js:

import api from '../../http-common';

export default new class ImagesService {
    //Read Screenshot
    readImages(imageName){
        return api.post('read/image', imageName)
    }
}

http-common.js:

import axios from 'axios'

export default axios.create({
    baseURL: 'http://localhost:3003/api',
    headers: {
        "Content-type": "application/json",
    }
})

Solution

  • it's because stream is not a promise: image is read and downloaded before the stream is finished saving it

    Try wrapping data.pipe in a promise and resolve when the saving is done:

    const downloadImage = (fileUrl, fileName) => {
        return new Promise(async(resolve, reject) => {
            let isDownloaded = false
            const downloadFolder = './images';
    
            const localFilePath = path.resolve(__dirname, downloadFolder, fileName);
    
            const imgStream = fs.createWriteStream(localFilePath);
    
            try {
                const response = await axios({
                    method: 'GET',
                    url: fileUrl,
                    responseType: 'stream',
                });
    
                if (response.status === 200) {
    
                    response.data.pipe(imgStream);
                }
    
    
            } catch (error) {
                console.log(error)
                reject(error);
            }
    
            imgStream.on('finish', () => {
                resolve(true);
            });
        })
    };