Image upload with react.js and node.js

24-12-2021

The past few days I needed to implement image upload for a project, something which I never had done before so it was time to learn. Here I share what I learned.

First let's describe the workflow, a user clicks on a button which prompts him to choose a file to upload then the user clicks on a submit button which sends the file to the server where it is saved in the filesystem and the path is saved to a database. We are going to be using multer, a node.js middleware that hanldes uploaded files so npm install it.

For the front-end you can use 2 approaches, a form or no form. The traditional approach with multer is a form, however I needed to implement it without using a form so I'll show both solutions.

Front-end

No form


    const FileUpload = () => {
      const [file, setFile] = useState(null);

      const handleFileChange = e => {
        setFile(e.target.files[0]);
      }

      const handleSubmit = () => {
        // Simulate a form
        const myForm = new FormData()

        // All mimetypes I want
        const types = ['image/png', 'image/jpeg', 'image/jpg']
        let errs = [];


        // Check mimetypes
        if (file && types.every(type => file?.type !== type)) {
          errs.push({ message: 'Invalid file, only png, jpeg and jpg is supported.' });
        }

        // Check file size
        if (file && file?.size > 2000000) {
          errs.push({ message: 'File is too large, pick something smaller than 2MB' });
        }

        /*
          Insert code here for handling errors
        */

        // If errors or no file, stop
        if(errs.length > 0 || !file) {
          return;
        }

        // Append file to form and give it the key of image
        myForm.append('image', file)

        fetch('/api/upload-image', {
          method: 'POST',
          body: myForm
        })
          .catch(err => console.log(err));
      }

      return (
        <div>
          <-- inputs with type file can not be controlled components -->
          <input type="file" onChange={handleFileChange} />

          <button onClick={handleSubmit}>
            Upload
          </button>
        </div>
      );
    }
  

Using a form


    const FileUpload = () => {
      return (
        <form action="/api/upload-image" method="post" enctype="multipart/form-data">
          <-- inputs with type file can not be controlled components -->
          <input type="file" name="image" />

          <input type="submit" />
        </form>
      );
    }
  

Backend


    const bodyParser = require('body-parser');
    const express = require('express');
    const path = require('path');
    const multer = require('multer');
    const upload = multer({
      // Destination of images
      dest: './public/assets',

      // Max filesize = 2MB, 1 file, 0 extra fields
      limits: { fileSize: 2000000, fields: 0, files: 1}
    });

    const app = express();

    app.use(cors());
    app.use(bodyParser.urlencoded({ extended: false }));
    app.use(bodyParser.json());

    // Serve images from public folder
    const dir = path.join(__dirname, 'public');
    app.use(express.static(dir));

    // Handle image upload
    app.post(
      '/upload-image',
      // This is how multer knows to get the file with the key 'image'
      upload.single('image'),
      (req, res) => {
        // Pseudocode where I add a new image with path '/public/assets/<filename>'
        db.images.insertNew({ path: '/assets/' + req.file.filename });

        res.json({ success: true });
      }
    );

    const port = process.env.PORT || 5000;

    app.listen(port, () => {
      console.log(`Server running on port ${port}`);
    });
  

You should now have a fully functional image upload utility. The images can be fetched from localhost:5000/assets/<filename>. You can learn more about multer right here.