How To Upload and Serve Photos Using React, Node, Express
Tags: javascript, react, node
Published: 2020-09-07 13:45:48

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.

```html
<input type="file" name="file" onChange={this.uploadHandler}/>
```

```javascript
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:

```javascript
  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](https://dev-to-uploads.s3.amazonaws.com/i/zz1pryqkh9xu99gmb1jg.png)

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:

```javascript
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:

```javascript
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.

```javascript
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.

```javascript
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) 

```javascript
        {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:

```javascript
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.