arrayspostgresqlvue.jsvuejs3bytea

Save Image Files as bytea in PostgreSQL and retrieving them to display on HTML template


Yes I know it's a bad idea/bad practice to save images in a database, nevertheless I have to as those are the requirements of my professor which is why I have to achieve it anyways.

The setup: I have a user table with an img column of type bytea where I want to store the image blob and later retrieve them through fetch and display them on my html template as base64.

onFileSelect I save the uploaded image to a variable which gets saved to the database with a fetch put request. This works so far and looks like this:

function onFileSelect(event) {
  img_upload = event.target.files[0];
  console.log(img_upload);
}

enter image description here

enter image description here

When I retrieve the data from the database I get back a bytearray which I can convert back from bytearray to a string. I also get the same result back as it's stored in the database:

enter image description here

But shouldn't it look more like this? How do I convert it to such a data URL so I can add it to my img :src?

img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4 //8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg=="

I tried all day to get this to work and I have no clue where my mistake lies with all this image conversion.. I've had to do a similar thing with MySQL Blob types once but that wasn't even remotely as difficult as this bytea stuff to figure out Your help would be appreciated


Solution

  • Alright, thanks to Daniel I've figured it out. As he guessed correctly my encoding was incorrect and I always got [object Object] for each value.

    Now it looks like this:

    OnFileSelect (Probably better to use an async await function here to receive the reader.result but for my simple case using timeout works just fine):

    function onFileSelect(event) {
      const reader = new FileReader();
      img_upload = event.target.files[0];
      reader.readAsDataURL(event.target.files[0]);
      setTimeout(function () {
        img_upload = reader.result;
        toggle_upload.value = true;
      }, 2000);
    }
    

    The value on img_upload is now a DataURL which I saved like that in my bytea column in my PostgreSQL user database.

    On page load:

    Fetching User and decoding the bytearray value:

    let profile_picture = ref("");
    function getUserImage() {
      const dec = new TextDecoder("utf-8");
      fetchUser(user.email).then((response) => {
        let img = response.img.data;
        profile_picture.value = dec.decode(new Uint8Array(img));
      });
    }
    

    Dynamically rendering the profile picture with Vues v-if conditional rendering. In case the user doesn't have an image yet, a default profile icon gets rendered:

        <img v-if="user.img === undefined || user.img === null" class="profile-img"
     src="../../assets/img/profile_default.png"/>
        
        <img v-else class="profile-img" :src="profile_picture" />
    

    On your backend you may also have to increase the json payload limits from the default 100kb to a bigger value (~5mb+) to allow for the huger payloads to be processed.

    In my case I use node.js Express for my backend. The command to increase the limit here is the following:

    app.use(json({ limit: "5mb" }));