Upload and display images in an existing MERN app

The easiest guide to using Multer for image handling

FRONT-END

1. Configure component

this.state = {
...,
images: []
}

Then add a handler function to update the state. Remember to bind the function, i.e.this.onChangeImages = this.onChangeImages.bind(this), in the constructor if not using arrow functions!

onChangeImages(e) {
this.setState({
images: e.target.files
})
})

2. Add user input into the DOM

<form onSubmit={this.onSubmit} encType="multipart/form-data">
<input
type=”file”
accept=”image/png, image/jpeg”
onChange={this.onChangeImages}
multiple
/>
</form>
File input

Multer requires multipart/form-data to work properly!

3. Implement onSubmit

If you also need to send a JSON object, append it to the FormData as a String since FormData takes either String or Blob values only.

onSubmit() {
const data = new FormData();
Array.from(this.state.images).forEach(image => {
data.append('file', image);
})
// append any other form data here, such as JSON object, as needed
// i.e. data.append('jsonObject', JSON.stringify(jsonObject));
axios.post('http://localhost:3000/add', data)
.then(res => console.log(res.data))
.catch(err => console.log(err))
}

Front end is now done!

BACK-END

1. Edit Mongoose schema.

images: { type: [Buffer] } 

2. Configure Multer.

$ npm i multer

and include the package in your app by adding to the top of your routes file

const multer = require(‘multer’);
const upload = multer()

Optional:

Add options to control and limit uploaded files.

function fileFilter(req, file, cb) {
if (file.mimetype === 'image/jpeg' // accept .jpeg
|| file.mimetype === 'image/png' // or .png files only
) {
cb(null, true);
} else {
cb(null, false);
}
}
const upload = multer({
limits: {
fileSize: 1024 * 1024, // limit the size of uploaded file to 1MB
files: 5 // limit the number of uploaded files to 5
},
fileFilter: fileFilter
})

Additional options can be found in the documentation.

3. Configure POST route

Then unpack each image as a buffer, as well as any JSON object by parsing the string back into a JSON object.

router.route('/add').post(upload.array('file'), (req, res) => {
let images = [];
req.files.forEach(file => images.push(file.buffer));
// read JSON object
// i.e. const json = req.body.jsonObject;
// const obj = JSON.parse(json);
...
}
Data in MongoDB
MongoDB stores buffer data as Binary objects

Now you can store images in your app!

DISPLAYING IMAGES

processBuffer(buffer) {
let binary = '';
let bytes = new Uint8Array(buffer);
bytes.forEach(byte => {
binary += String.fromCharCode(byte)
})
return window.btoa(binary);
}

Use this function to generate the base64 encoded image data for each image path.

{this.state.images.map(image => {
<img
src={`data:image/png;base64,${this.processBuffer(image.data)}`}
alt="Image"
/>
})}

Boom! Done.