Upload and display images in an existing MERN app

The easiest guide to using Multer for image handling

Jenny Xin
3 min readMar 30, 2020

FRONT-END

1. Configure component

Add a property and handler for the images to the component state.

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

Construct a FormData() object to send the POST request and append each of the images to a property named ‘file’ (can be whatever name as long as it matches the POST route).

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.

In your object model, define a property of type Buffer.

images: { type: [Buffer] } 

2. Configure Multer.

Install Multer by running

$ 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

Add the middleware where ‘file’ matches the key in the FormData object sent from the front-end.

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

Once the images are retrieved from your database and loaded to your component state, convert the buffer back into images by converting it to a base64 String.

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.

--

--