0

I convert images into base64 strings before sending them to a NodeJS server. Then, I store base64 strings into MongoDB. The problem is that when I try to get my base64 strings from the database, it takes an extremely long time (about 1 minute for 10 images). And my website deals with a lot of images.

Here is my React code :

import React, { Component, useState, useCallback } from "react"
import Header from '../Header'
import ModalCountry from '../ModalCountry'
import Photo from './Photo'
import Gallerie from './Gallerie';
import ReactFileReader from 'react-file-reader'
import Config from '../../Config'
import './styles.css'

class Photos extends Component {
    constructor(props){
        super(props)

        this.state = {
            countryAlpha3: this.props.location.state ? this.props.location.state.lookedAlpha3 : "",
            photos: [],
            showAddPhotos: "d-none",
            photosToAdd: []
        }

        this.handleFiles = this.handleFiles.bind(this);
        this.storePhotos = this.storePhotos.bind(this);
    }

    handleFiles(files){
        let photosStamp = [];
        files.base64.forEach(function(file){
            photosStamp.push({
                photo: file,
                base64: file,
                title: "Titre",
                description: "Description"
            })
        })
        this.setState({
            photosToAdd: photosStamp
        }, () => console.log(this.state.photosToAdd))

        this.setState({
            showAddPhotos: 'd-block'
        })
    }

    componentDidMount(){
        var photosArray = this.state.photos;
        let self = this;
        fetch(Config.apiUrl + 'photos_thisUser/' + this.state.countryAlpha3, {
            method: 'GET',
            credentials: 'include',
            headers: {
              'Content-Type': 'application/json',
              "Authorization": localStorage.getItem("xsrfToken")
            }
          })
          .then(response => response.json())
          .then(body => {
              body.forEach(function(data){
                  self.setState({
                      photos: self.state.photos.concat({
                          photo: data.base64,
                          caption: data.title,
                          subcaption: data.description
                      })
                  })
              })
          })
          .catch(err => {
              console.log(err)
          })
    }

    storePhotos(){
        fetch(Config.apiUrl + 'addPhotos', {
            method: 'PUT',
            credentials: 'include',
            body: JSON.stringify({
              'photos': this.state.photosToAdd,
              'alpha3': this.state.countryAlpha3
            }),
            headers: {
              'Content-Type': 'application/json',
              "Authorization": localStorage.getItem("xsrfToken")
            }
          })
          .then(response => response.json())
          .then(body => {
            this.setState({
                photos: this.state.photos.concat(this.state.photosToAdd),
                showAddPhotos: "d-none",
                photosToAdd: []
            })
          })
          .catch(err => {
              console.log(err)
          })
    }


    render() {   

        return (
            <div className="mainPhotos">
                {this.state.redirection}
                <div className="profileHeader">
                    <Header openModal={this.getDataFromSearchBar} />
                </div>
                <ModalCountry ref={this._child} modalTitle={this.state.modalTitle} alpha3={this.state.alpha3} />
                <div className="header">
                    <div className="banner col-md-12 row">               
                        <div className="col-md-12 titlePhotos text-center">
                            Photos de {this.state.countryName}
                        </div>
                    </div>
                </div>
            <div className="photosContent">               
                <div className="text-center">
                    <ReactFileReader fileTypes={[".png", ".jpg", ".jpeg", ".bmp"]} base64={true} multipleFiles={true} handleFiles={this.handleFiles}>
                        <button className="btn btn-info">Ajouter des photos</button>
                    </ReactFileReader>
                </div>

                <div className={"photosToAdd row text-center " + (this.state.showAddPhotos)}>
                    <div className="photosFull photosToAddWithButton">
                        {this.state.photosToAdd.map((item, index) => <Photo src={item.photo} openLightbox={() => {console.log("opened")}} />)}
                    </div>
                    <div className="col-md-12 text-center">
                        <button
                            className="btn btn-success form-control btnValid col-md-1"
                            onClick={this.storePhotos}
                        >
                            Ajouter
                        </button>
                        <button
                            className="btn btn-danger form-control btnCancel col-md-1"
                            onClick={this.cancelAddPhotos}
                        >
                            Annuler
                        </button>
                    </div>
                </div>           

                <div className="photosContent row">
                    <div className="photosFull">
                        {this.state.photos.map((item, index) => <Photo src={item.photo} openLightbox={() => {this.setState({
                            activePhotoIndex: index
                        }, this.setState({
                            galleryIsVisible: true
                        }))}} />)}
                        <Gallerie photos={this.state.photos} activePhotoIndex={this.state.activePhotoIndex} galleryIsVisible={this.state.galleryIsVisible} close={() => this.setState({
                            galleryIsVisible: false
                        })} />
                    </div>
                </div>
            </div>
            </div>
        )
    }
}

export default Photos

Here is my nodeJS code :

var express = require('express')
var router = express.Router()
const Photo = require('../../models/Photo')
const Country = require('../../models/Country')
let mongoose = require('mongoose')

router.put('/addPhotos', function(req, res, next){
    if(req.body.alpha3 && req.body.photos){
        Country.findOne({ alpha3: req.body.alpha3 })
        .then(country => {
            console.log(req.body.photos)
            req.body.photos.forEach(function(photo){
                photoToAdd = new Photo({
                    country: mongoose.Types.ObjectId(country._id),
                    user: mongoose.Types.ObjectId(req.decoded.id),
                    base64: photo.base64,
                    title: photo.title,
                    description: photo.description
                });
                photoToAdd.save(function(err, addedPhoto){
                })
            })
            {
                res.json({
                    success: true,
                    message: "Photos added"
                })
            }
        })
    }
    else
    {
        res.json({
            error: req.body.alpha3
        })
    }
})


router.get('/photos_thisUser/:alpha3', function(req, res, next){
        Country.findOne({ alpha3: req.params.alpha3 })
        .then(country => {
            Photo.find({ user: req.decoded.id, country: country._id }, {_id: 0, country: 0})
            .then(photos => {
                res.json(photos)
            })
            .catch(err => {
                res.status(400).send({
                    success: false,
                    message: "Error: " + err
                })
            })
        })
        .catch(err => {
            res.status(400).send({
                success: false,
                message: "Error: " + err
            })
        })
})

module.exports = router

Here is the mongoose schema :

let mongoose = require('mongoose');

const PhotoSchema = new mongoose.Schema({
    country: {
        type: mongoose.Schema.Types.ObjectId,
        ref: 'Country'
    },
    user: {
        type: mongoose.Schema.Types.ObjectId,
        ref: 'User'
    },
    base64: {
        type: String,
        required: true
    },
    title: {
        type: String
    },
    description: {
        type: String
    }
});

let PhotoModel = mongoose.model('Photo', PhotoSchema);

module.exports = PhotoModel

Maybe I shouldn't store base64 data into a string in mongo? I would like to display images one by one, then the user won't have to wait as long. But how can I update only a part of the state? Or is it the wrong technique to use base64 in my website?

Wyck
  • 10,311
  • 6
  • 39
  • 60
Kuartz
  • 302
  • 2
  • 5
  • 23
  • I think instead of storing the base64 in your DB you should store the image to your application folder and then save the file path in your MongoDB collection. You can have a separate folder for storing the image inside the application folder. – Sathishkumar Rakkiyasamy Sep 12 '19 at 10:01

1 Answers1

1

Store your images as base64 encoded string is not a good idea since them would be approximately 37% larger than their original binary representation.
If your website is image based you should save your images somewhere.
As on premise solution you can store them in your server and then just save image path on your db, otherwise you can use a cloud solution , AWS S3 for instance, and then store and retrieve images using their api's.
If your website has a lot of images and visits, though them are not free, i suggest to use some cloud solution since them come (almost) out of the box with some cool features like caching and image manipulation and you don't have to take care of disk space or server performance. retrieve a (possibly cached) scaled image, would be always faster than base64 encoded, or a fullsize image saved on your server. Regarding the frontend you can use some react lazy-loading module to load and display your pictures when you need it.

Nico
  • 6,259
  • 4
  • 24
  • 40
  • Ok thank you for your help. So I can use cloud servers to store my images or store them into a folder in my react application, then store the path into mongoDB. But as I thought, base64 should not be used here because of bad performances it gives. Thank you very much for your advice ! – Kuartz Sep 12 '19 at 12:03