Search code examples
mongodbreactjsexpressaxiosmulter

Uploading an image to mongodb using Multer with Express and Axios


I am basically trying to make a small application which allows an admin user to enter a name, price and image of a product which can then be viewed on another page. The details will be sent to a mongo database which will be performed via an axios post from the front end. I can send the name and the price no problem which can be seen on the front end dynamically, however, I am unable to send image to the mongo database which i've been trying to achieve now for quite some time.

I am using multer and axios to try and sent the file over as the application is a react app. I think the problem is to do with the "req.file" within the back end of the application. The code below is my endpoint:

api.js

var express     = require('express');
var bodyParser  = require('body-parser');
var cors        = require('cors')
var app         = express();
var mongodb     = require('mongodb');
var path        = require('path');
var fsextra     = require('fs-extra');
var fs          = require('fs')
var util        = require('util')
var multer      = require('multer')
var upload      = multer( {dest:  __dirname + '/uploads'} )
var ejs         = require('ejs')

const MongoClient = require('mongodb').MongoClient;

app.use(express.static(path.resolve(__dirname, '../react', 'build')));
app.get('*',(req,res)=>{
  res.sendFile(path.resolve(__dirname, '../react', 'build', 'index.html'));
});
console.log(__dirname)
app.use(cors());
app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json());
app.use(express.static(path.join(__dirname, 'public')));
app.set('views', __dirname);
app.engine('html', require('ejs').renderFile);
app.set('view engine', 'html');
var db;

mongodb.MongoClient.connect('mongodb://<mydbdetails>', (err, database) => {
  if (err) {
    console.log(err)
    process.exit(1);
  }
  db = database;
  console.log('Database connection is ready')

});
var server= app.listen(process.env.PORT || 8082, function () {
  var port = server.address().port;
  console.log("App now running on port", port);
});



app.post('/api/submitImage', upload.single('inputForm'), function(req,res){
  var file = req.body.file
    if (file == null) {
    // If Submit was accidentally clicked with no file selected...
      //res.render('admin', { title:'Please select a picture file to submit!'});
      res.send({success: false, message: "dsfdsg"})
      console.log('There is no file present')
      console.log(req.file,'file')
  }
  else{
    // read the img file from tmp in-memory location
   var newImg = fs.readFileSync(req.files.path);
   console.log(newImg,'details of the new image')
   // encode the file as a base64 string.
   var encImg = newImg.toString('base64');
   console.log(encImg,'kdfjndodj')
   // define your new document
   var newItem = {
      description: req.body.description,
      contentType: req.file.mimetype,
      size: req.files.size,
      img: Buffer(encImg, 'base64')
    };
    db.collection('products').insert(newItem, function(err, result){
      if(err) {
        console.log(err)
      }
      var newoid = new ObjectId(result.ops[0]._id);
      fs.remove(req.file.path, function(err) {
        if (err) { console.log(err) };
        res.render('./src/components/adminContainer.js', {title:'Thanks for the Picture!'});
      });
    })
  }
})

The next code is the how I am trying to send it over using Axios:

import axios from 'axios';

class ProductsApi {

  static submitProduct(name,prices,callback){

      axios.post('http://localhost:8082/api/submitProduct', {name: name, prices: prices})
        .then( response => {
        callback(response)
    })
  }
  static viewName(callback){
    axios.post('http://localhost:8082/api/retrieveName')
      .then( response => {
        return callback(response)
    })
  }
  static viewPrice(callback){
    axios.post('http://localhost:8082/api/retrievePrice')
      .then( response => {
        return callback(response)
    })
  }
  static viewProducts(callback){
    axios.post('http://localhost:8082/api/retrieveProducts')
      .then( response => {
        return callback(response)
    })
  }
  static submitImages(image,callback){
    axios.post('http://localhost:8082/api/submitImage',{image: image})
      .then( response => {
        return callback(response)
        console.log('response has been made,', image,'has been recieved by axios')
    })
  }
}


export default ProductsApi;

The last file is how I am trying to send the file to the database using react with event handlers:

  import React, { Component } from 'react'
  import '../App.css'
  import AppHeader from './appHeader.js'
  import ProductsApi from '../api/axios.js'

  const AdminContainer = () => {
    return(
      <div>
      <AppHeader />
      <FormContainer />
      </div>
    )
  }

  class FormContainer extends Component{
    constructor(props){
      super(props);
      this.state={
        file: '',
        inputName: '',
        inputPrice: '',
        image: ''
      };
      this.handleNameChange = this.handleNameChange.bind(this);
      this.handlePriceChange = this.handlePriceChange.bind(this);
      this.handleSubmit = this.handleSubmit.bind(this);
      this.sendName = this.handleSubmit.bind(this);
    }

    handleNameChange(e){
        console.log(e.target.value)
      this.setState({
        name : e.target.value,
      })
    }
    handlePriceChange(e){
        console.log(e.target.value)
      this.setState({
        prices : e.target.value
      })
    }
    sendName(e){
      this.setState({
        inputName: e.target.value,
        inputName:e.target.value
      })
    }

    handleSubmit(e){
      e.preventDefault();


    console.log('attempting to access axios...')
    ProductsApi.submitProduct(this.state.name, this.state.prices, resp => {
        console.log('response has been made', resp)
        //if error message, add to state and show error message on front end
        this.setState({
          inputName:this.state.name,
          inputPrice:this.state.prices
        },function(){
          console.log(resp,'this is resp')
          console.log('Axios has send ',this.state.name,' to the database')

        });
      })

      console.log(this.state.prices,'This is the new price')
      console.log(this.state.name,'This is the new name')

    ProductsApi.submitImages(this.state.image, response => {
        console.log('axios has been notified to submit an image...')
        this.setState({
          image: this.state.image
        },function(){
          console.log('Image submission axios response details are as follows: ', response)
          console.log(this.state.image, ': has been sent to the db')
        })
    })
  }

    render(){
      return(

        <div>
          <h2>Add a new product to the Shop</h2>
          <div className='formWrapper'>
            <div className='center'>
              <form name='inputForm' encType='multipart/form-data' method='post'>
                <label>
                  Name:
                  <input value = {this.state.name} onChange={this.handleNameChange} type="text" placeholder='Name' /><br />
                  Price:
                  <input value = {this.state.prices} onChange={this.handlePriceChange} type='text' /><br />
                </label>
                <label>
                  Choose an Image:
                  <input className='imgInsert' name ='inputForm' type='file'/>
                </label>
                <div>
                <img className = 'previewImage' value={this.state.image}/>
                </div>
                <button className='btn updateBtn' onClick={(e) => this.handleSubmit(e)}>Submit</button>
              </form>
            </div>
          </div>
        </div>
      )
    }

  }


  export default AdminContainer

Common errors I am getting when trying debug it is

TypeError: Cannot read property 'path' of undefined."

and "file" being undefined.


Solution

  • When using multer to save images you need to make sure that the image comes to the server as form data. this is because multer requires the multipart/form-data encoding which you do not get when submitting a form with an ajax request unless if you specifically do something to make it happen. You can do this by using the FormData object. Here is an example of this being used. I hope this helps.