Upload and display images in an existing MERN app
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>
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);
...
}
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.