loading...

How To Upload and Serve Photos Using React, Node, Express

austinbrownopspark profile image Austin Brown ・4 min read

In this blog, I'll be going over the simplest and fastest method to upload a photo from a React client to a Node server with Express and then how to display that photo back to the client.

Assuming that you already have these components of your app set up and connected, first we'll start with a basic button and a function to handle the selected file.

<input type="file" name="file" onChange={this.uploadHandler}/>
uploadHandler(event) {
  // code goes here
}

In this demo, it should be noted that I will be showing examples using a class component, but you could just as well do this in a functional component.

Now, inside your upload handler function, you'll need to convert the file into something that can be sent in a POST request, and then send it to your server. We'll be using Axios in this example.

Here's what the upload handler function should look like:

  uploadHandler(event) {
    const data = new FormData();
    data.append('file', event.target.files[0]);

    axios.post('/upload', data)
      .then((res) => {
        this.setState({ photos: [res.data, ...this.state.photos] });
      });
  }

Here, we are creating an instance of FormData and appending the file with .append(). The file details can also be seen with a console log of event.target.files[0], in which case you may see something that looks like this:
Alt Text

Now, for your Express server, you'll need to install Multer if you do not have it already, via the command npm install multer. Then, add this to the .js file for your Express server above wherever your POST route is going to be:

const multer = require('multer')

const storage = multer.diskStorage({
  destination: function (req, file, cb) {
    cb(null, 'public')
  },
  filename: (req, file, cb) => {
    cb(null, Date.now() + '-' +file.originalname)
  }
})

const upload = multer({ storage: storage }).single('file')

All we really need from here is the upload function which is built from storage and multer above it. In the multer.diskStorage object above, the 'public' string in 'destination:' can be changed to whatever you want the folder name to be where your photos to be stored. This folder, by default, is in the root folder of your entire app.
Additionally, the Date.now() + '-' +file.originalname below it specifies the filename that your stored photo will be saved as. If you leave it as is, the original filename will be preserved, but with a JavaScript formatted date in front of it followed by a dash.

And now for the POST route:

app.post('/upload', (req, res) => {
  upload(req, res, (err) => {
    if (err) {
      res.sendStatus(500);
    }
    res.send(req.file);
  });
});

As you can see, the aforementioned upload() function is now handling the req and res objects from the initial Express app.post. We have some basic error handling by sending a 500 if the file can't be saved, but otherwise, it will send back information about the file that was saved. There's just one thing left that the server will need now in order to actually serve that file back to the client.

app.use(express.static('public'));

Add this near the bottom of your express server index.js file. The 'public' string here again will refer to whatever you have named the folder that will store the image files. Now let's look back at the Axios request from the client.

axios.post('/upload', data)
      .then((res) => {
        this.setState({ photos: [res.data, ...this.state.photos] });
      });

In the .then(), res.data contains an object with details about the file that was saved, one of them being the filename. this.setState({ photos: [res.data, ...this.state.photos] }); will add this object to the front of a 'photos' array in this.state.

Now, in your render(), beneath the upload button, you could add something like this: (where localhost is the host your app is being served from and 3000 is your port number)

        {this.state.photos.map(photo => (
          <img src={`http://localhost:3000/${photo.filename}`} />
        ))}

By default, with app.use(express.static('public')), the photos inside the 'public' folder will be available at the '/' endpoint followed by the filename. So the above map function should now display the photos one by one as you add them, with the most recent being at the top since we're adding them in reverse order.

Here is the final result of the code for the client side app:

import React, { Component } from 'react';
import axios from 'axios';

class App extends Component {
  constructor(props) {
    super(props);

    this.state = {
      photos: [],
    };

  this.uploadHandler = this.uploadHandler.bind(this);
  }

  uploadHandler(event) {
    const data = new FormData();
    data.append('file', event.target.files[0]);
    axios.post('/upload', data)
      .then((res) => {
        this.setState({ photos: [res.data, ...this.state.photos] });
      });
  }

  render() {
    return  (
      <div>
        <div>
          <input type="file" name="file" onChange={this.uploadHandler}/>
        </div>
        {this.state.photos.map(photo => (
          <img src={`http://localhost:3000/${photo.filename}`} />
        ))}
      </div>
    )
  }
}

export default App;

The demonstration that I've done shows the quickest, most basic way to get a file upload and retrieval system working with React, Node, and Express. You will most likely want to eventually add more advanced features like multiple file upload, more advanced error handling, a progress indicator, saving the URLs to a database, and possibly a separate process of selecting the file and uploading the file. However, the demo should be enough to get you started. There is a lot that can be done with file upload functionality in an app, so hopefully this demo will help get you started.

Discussion

markdown guide