Build File Upload/Download Functionality with Image Preview using MERN stack
Tags: react, javascript, node, codenewbie
Published: 2020-10-09 04:25:51

### Introduction

In this article, we will create a file upload and download functionality with a preview of the image using the MERN stack.

By creating this App, you will learn

- How to upload a file using drag and drop
- How to upload and download any type of file
- How to restrict the type of the file while uploading
- How to restrict the size of the file while uploading
- How to show a preview of the image after selecting it
- How to use MongoDB to store and get the details of the file

and much more.

> Instead of storing the file in the MongoDB database as base64 encoded, we will be storing the file on the server and only storing the path of the file inside the database so as to keep the database size smaller and to easily access and move the files as per the need.

We're using a very popular [react-dropzone](https://www.npmjs.com/package/react-dropzone) npm library for implementing the drag and drop functionality.

For actual file upload, we're using [multer](https://www.npmjs.com/package/multer) npm library which is also very popular for uploading files.

We will be using React Hooks for building this application, so If you're not familiar with it, check out my previous article [here](https://levelup.gitconnected.com/an-introduction-to-react-hooks-50281fd961fe?source=friends_link&sk=89baff89ec8bc637e7c13b7554904e54) for the introduction to Hooks.

We will be using the `MongoDB` database so make sure you install it locally by following my previous article [here](https://levelup.gitconnected.com/how-to-install-mongodb-database-on-local-environment-19a8a76f1b92?source=friends_link&sk=416b443bad1f86b292e4b72602cf5c9b)

## Initial Setup

Create a new project using `create-react-app`

```js
create-react-app react-upload-download-files
```

Once the project is created, delete all files from the `src` folder and create `index.js` and `styles.scss` files inside the `src` folder. Also create `components`, `router` and `utils` folders inside the `src` folder.

Install the necessary dependencies:

```js
yarn add axios@0.20.0 bootstrap@4.5.2 downloadjs@1.4.7 node-sass@4.14.1 react-bootstrap@1.3.0 react-dropzone@11.2.0 react-router-dom@5.2.0
```

Open `styles.scss` and add the contents from [here](https://github.com/myogeshchavan97/react-upload-download-files/blob/master/src/styles.scss) inside it.

## Creating Initial Pages

Create a new file with name `Header.js` inside the `components` folder with the following content:

```js
import React from 'react';
import { NavLink } from 'react-router-dom';

const Header = () => {
  return (
    <div className="header">
      <h1>File Upload And Download</h1>
      <nav>
        <NavLink activeClassName="active" to="/" exact={true}>
          Home
        </NavLink>
        <NavLink activeClassName="active" to="/list">
          Files List
        </NavLink>
      </nav>
    </div>
  );
};

export default Header;
```

Create a new file with name `App.js` inside the `components` folder with the following content:

```js
import React, { useState, useRef } from 'react';
import { Form, Row, Col, Button } from 'react-bootstrap';

const App = (props) => {
  const [file, setFile] = useState(null); // state for storing actual image
  const [previewSrc, setPreviewSrc] = useState(''); // state for storing previewImage
  const [state, setState] = useState({
    title: '',
    description: ''
  });
  const [errorMsg, setErrorMsg] = useState('');
  const [isPreviewAvailable, setIsPreviewAvailable] = useState(false); // state to show preview only for images
  const dropRef = useRef(); // React ref for managing the hover state of droppable area

  const handleInputChange = (event) => {
    setState({
      ...state,
      [event.target.name]: event.target.value
    });
  };

  const handleOnSubmit = async (event) => {
    event.preventDefault();
  };

  return (
    <React.Fragment>
      <Form className="search-form" onSubmit={handleOnSubmit}>
        {errorMsg && <p className="errorMsg">{errorMsg}</p>}
        <Row>
          <Col>
            <Form.Group controlId="title">
              <Form.Control
                type="text"
                name="title"
                value={state.title || ''}
                placeholder="Enter title"
                onChange={handleInputChange}
              />
            </Form.Group>
          </Col>
        </Row>
        <Row>
          <Col>
            <Form.Group controlId="description">
              <Form.Control
                type="text"
                name="description"
                value={state.description || ''}
                placeholder="Enter description"
                onChange={handleInputChange}
              />
            </Form.Group>
          </Col>
        </Row>
        <Button variant="primary" type="submit">
          Submit
        </Button>
      </Form>
    </React.Fragment>
  );
};

export default App;
```

In this file, we're rendering a form to add the `title` and `description` for now. We will add the option to add the file later in this article.

For each input field, we have added an `handleInputChange` handler that updates the state of each input field. We have added a `name` attribute to each input field which matches exactly with the name of the state variables so we're able to use ES6 shorthand syntax for updating the state.

```js
const handleInputChange = (event) => {
  setState({
    ...state,
    [event.target.name]: event.target.value
  });
};
```

In the case of Hooks, the state is not merged automatically, so we're first spreading all the properties of the state and then updating the respective input field.

Create a new file with name `AppRouter.js` inside the `router` folder with the following content:

```js
import React from 'react';
import { BrowserRouter, Switch, Route } from 'react-router-dom';
import App from '../components/App';
import Header from '../components/Header';

const AppRouter = () => (
  <BrowserRouter>
    <div className="container">
      <Header />
      <div className="main-content">
        <Switch>
          <Route component={App} path="/" exact={true} />
        </Switch>
      </div>
    </div>
  </BrowserRouter>
);

export default AppRouter;
```

Now, open `src/index.js` file and add the following contents inside it:

```js
import React from 'react';
import ReactDOM from 'react-dom';
import AppRouter from './router/AppRouter';
import 'bootstrap/dist/css/bootstrap.min.css';
import './styles.scss';

ReactDOM.render(<AppRouter />, document.getElementById('root'));
```

Now, start the application by executing the `yarn start` command from the terminal.

You will see the following screen:

![Initial Screen](https://gist.github.com/myogeshchavan97/e0be7fc4c838544e2d00afeb3a82ae10/raw/0dc8da319bd7b652e2f6ac9fa0f97f58b7df8c91/initial_screen.png)

## Adding File Upload Functionality

Now, let's add the option to upload the file from the UI.

Open `src/App.js` file and before the submit button and after the ending `Row` tag, add the following code

```js
<div className="upload-section">
  <Dropzone onDrop={onDrop}>
    {({ getRootProps, getInputProps }) => (
      <div {...getRootProps({ className: 'drop-zone' })} ref={dropRef}>
        <input {...getInputProps()} />
        <p>Drag and drop a file OR click here to select a file</p>
        {file && (
          <div>
            <strong>Selected file:</strong> {file.name}
          </div>
        )}
      </div>
    )}
  </Dropzone>
  {previewSrc ? (
    isPreviewAvailable ? (
      <div className="image-preview">
        <img className="preview-image" src={previewSrc} alt="Preview" />
      </div>
    ) : (
      <div className="preview-message">
        <p>No preview available for this file</p>
      </div>
    )
  ) : (
    <div className="preview-message">
      <p>Image preview will be shown here after selection</p>
    </div>
  )}
</div>
```

Here, we're using the `DropZone` component with React render props pattern where the text we need to display in the drop area is added after the input field inside the `DropZone` component.

Add the import for `DropZone` and `axios` at the top of the `App.js` file.

```js
import Dropzone from 'react-dropzone';
import axios from 'axios';
```

Add the `OnDrop` function after the `handleInputChange` handler.

```js
const onDrop = (files) => {
  const [uploadedFile] = files;
  setFile(uploadedFile);

  const fileReader = new FileReader();
  fileReader.onload = () => {
    setPreviewSrc(fileReader.result);
  };
  fileReader.readAsDataURL(uploadedFile);
  setIsPreviewAvailable(uploadedFile.name.match(/\.(jpeg|jpg|png)$/));
};
```

Here, the `onDrop` function receives a files array with the dropped or selected files.

We're uploading only one file at a time so the uploaded file will be available files[0] so we're using array destructuring syntax to get that file value.

```js
const [uploadedFile] = files;
```

To display the preview of the image, we're using JavaScript `FileReader` API.

To convert the file to `dataURL` we call the `fileReader.readAsDataURL` method.

Once the file is successfully read as `dataURL`, the onload function of `fileReader` will be called.

```js
fileReader.onload = () => {
  setPreviewSrc(fileReader.result);
};
```

The result of the read operation will be available in the `result` property of the `fileReader` which we're assigning to the `previewSrc` state variable.

We're showing preview only for images so we're checking if the uploaded file is of correct format (only jpg, jpeg and png image) and updating the state of `previewAvailable` variable.

```js
setIsPreviewAvailable(uploadedFile.name.match(/\.(jpeg|jpg|png)$/));
```

Now, restart the application by running the `yarn start` command and verify the functionality.

![Upload Preview](https://gist.github.com/myogeshchavan97/e0be7fc4c838544e2d00afeb3a82ae10/raw/0dc8da319bd7b652e2f6ac9fa0f97f58b7df8c91/upload_preview.gif)

Here, we've added a file by browsing it. You can even add a file by drag and drop as shown below.

![Drag and Drop File](https://gist.github.com/myogeshchavan97/e0be7fc4c838544e2d00afeb3a82ae10/raw/0dc8da319bd7b652e2f6ac9fa0f97f58b7df8c91/drag_drop.gif)

If you select a file, other than an image, we'll not show the preview indicated by the message `No preview available for this file`.

![No Preview](https://gist.github.com/myogeshchavan97/e0be7fc4c838544e2d00afeb3a82ae10/raw/0dc8da319bd7b652e2f6ac9fa0f97f58b7df8c91/no_preview.gif)

## Add drop indication

If you saw the drop functionality, we're not showing any indication that the file is being dropped into the drop area so let's add that.

We've already added a `ref` to the div with class `drop-zone` inside the `App.js` file.

```js
<div {...getRootProps({ className: 'drop-zone' })} ref={dropRef}>
```

and also created the `dropRef` variable at the top using `useRef` hook.

Add the `onDragEnter` and `onDragLeave` props to the `Dropzone` component.

```js
<Dropzone
  onDrop={onDrop}
  onDragEnter={() => updateBorder('over')}
  onDragLeave={() => updateBorder('leave')}
>
```

The `onDragEnter` function will be triggered when the file is over the drop area and `onDragLeave` function will be triggered when the file is removed from the drop area.

Create a new `updateBorder` function inside the `App` component before the `handleOnSubmit` handler.

```js
const updateBorder = (dragState) => {
  if (dragState === 'over') {
    dropRef.current.style.border = '2px solid #000';
  } else if (dragState === 'leave') {
    dropRef.current.style.border = '2px dashed #e9ebeb';
  }
};
```

As we've added the `dropRef` ref to the div with class `drop-zone`, it will point to that div and we can use its `current` property to update the border of the drop area using `dropRef.current.style.border`.

Also, inside the `onDrop` function, add the following line at the end of the function.

```js
dropRef.current.style.border = '2px dashed #e9ebeb';
```

so when we drop the file over the drop area, the border will return to its normal state.

Now, If you check the application, you will see the dropping effect with the changing border.

![Drop Effect](https://gist.github.com/myogeshchavan97/e0be7fc4c838544e2d00afeb3a82ae10/raw/0dc8da319bd7b652e2f6ac9fa0f97f58b7df8c91/drop_effect.gif)

## Calling API For File Upload

Create a new file with the name `constants.js` inside the `src/utils` folder with the following content

```js
export const API_URL = 'http://localhost:3030';
```

We will be starting our Express server on port `3030` soon so we have mentioned that here.

Now, let's write the code inside the `handleOnSubmit` handler of `App.js` to call the backend API.

Replace the `handleOnSubmit` handler with the following code

```js
const handleOnSubmit = async (event) => {
  event.preventDefault();

  try {
    const { title, description } = state;
    if (title.trim() !== '' && description.trim() !== '') {
      if (file) {
        const formData = new FormData();
        formData.append('file', file);
        formData.append('title', title);
        formData.append('description', description);

        setErrorMsg('');
        await axios.post(`${API_URL}/upload`, formData, {
          headers: {
            'Content-Type': 'multipart/form-data'
          }
        });
      } else {
        setErrorMsg('Please select a file to add.');
      }
    } else {
      setErrorMsg('Please enter all the field values.');
    }
  } catch (error) {
    error.response && setErrorMsg(error.response.data);
  }
};
```

Also, import the `API_URL` at the top of the file.

```js
import { API_URL } from '../utils/constants';
```

Inside the `handleOnSubmit` handler, we're first checking if the user has entered all the field values and selected the file and we're making an API call to `/upload` API which we will be writing in the next section.

```js
await axios.post(`${API_URL}/upload`, formData, {
  headers: {
    'Content-Type': 'multipart/form-data'
  }
});
```

We're making a `POST` request with the `formData` object and sending `title`, `description` and the actual file to the API.

Note that, mentioning the content type of multipart/form-data is very important otherwise the file will not be sent to the server.

## Adding server-side code for file upload

Now, let's add the server-side functionality to upload the file.

Create a folder with name `server` inside the `react-upload-download-files` folder and execute the following command from the `server` folder

```js
yarn init -y
```

This will create a `package.json` file inside the `server` folder.

Install the required dependencies by executing the following command from the terminal from inside the `server` folder

```js
yarn add cors@2.8.5 express@4.17.1 mongoose@5.10.7 multer@1.4.2 nodemon@2.0.4
```

Create a new file with the name `.gitignore` inside the `server` folder and add the following line inside it so `node_modules` folder will not be added in your Git repository.

```js
node_modules
```

Now create `db`, `files`, `model`, `routes` folder inside the `server` folder. Also, create `index.js` inside the `server` folder.

Inside the `server/db` folder, create a new file `db.js` with the following content

```js
const mongoose = require('mongoose');

mongoose.connect('mongodb://127.0.0.1:27017/file_upload', {
  useNewUrlParser: true,
  useUnifiedTopology: true,
  useCreateIndex: true
});
```

Provide your `MongoDB` database connection details here. `file_upload` is the name of the database we will use.

Create a new file with name `file.js` inside the `model` folder with the following content

```js
const mongoose = require('mongoose');

const fileSchema = mongoose.Schema(
  {
    title: {
      type: String,
      required: true,
      trim: true
    },
    description: {
      type: String,
      required: true,
      trim: true
    },
    file_path: {
      type: String,
      required: true
    },
    file_mimetype: {
      type: String,
      required: true
    }
  },
  {
    timestamps: true
  }
);

const File = mongoose.model('File', fileSchema);

module.exports = File;
```

Here, we have defined the schema for the collection as we're using a very popular [mongoose](https://mongoosejs.com/) library to work with MongoDB. We will be storing the `title`, `description`, `file_path` and `file_mimetype` in the collection so we have described the type of each in this file.

Note that, even though we have defined the model name as `File`, MongoDB creates a plural version of the collection. So the collection name will be `files`.

Now, create a new file with name `file.js` inside the `routes` folder with the following content

```js
const path = require('path');
const express = require('express');
const multer = require('multer');
const File = require('../model/file');
const Router = express.Router();

const upload = multer({
  storage: multer.diskStorage({
    destination(req, file, cb) {
      cb(null, './files');
    },
    filename(req, file, cb) {
      cb(null, `${new Date().getTime()}_${file.originalname}`);
    }
  }),
  limits: {
    fileSize: 1000000 // max file size 1MB = 1000000 bytes
  },
  fileFilter(req, file, cb) {
    if (!file.originalname.match(/\.(jpeg|jpg|png|pdf|doc|docx|xlsx|xls)$/)) {
      return cb(
        new Error(
          'only upload files with jpg, jpeg, png, pdf, doc, docx, xslx, xls format.'
        )
      );
    }
    cb(undefined, true); // continue with upload
  }
});

Router.post(
  '/upload',
  upload.single('file'),
  async (req, res) => {
    try {
      const { title, description } = req.body;
      const { path, mimetype } = req.file;
      const file = new File({
        title,
        description,
        file_path: path,
        file_mimetype: mimetype
      });
      await file.save();
      res.send('file uploaded successfully.');
    } catch (error) {
      res.status(400).send('Error while uploading file. Try again later.');
    }
  },
  (error, req, res, next) => {
    if (error) {
      res.status(500).send(error.message);
    }
  }
);

Router.get('/getAllFiles', async (req, res) => {
  try {
    const files = await File.find({});
    const sortedByCreationDate = files.sort(
      (a, b) => b.createdAt - a.createdAt
    );
    res.send(sortedByCreationDate);
  } catch (error) {
    res.status(400).send('Error while getting list of files. Try again later.');
  }
});

Router.get('/download/:id', async (req, res) => {
  try {
    const file = await File.findById(req.params.id);
    res.set({
      'Content-Type': file.file_mimetype
    });
    res.sendFile(path.join(__dirname, '..', file.file_path));
  } catch (error) {
    res.status(400).send('Error while downloading file. Try again later.');
  }
});

module.exports = Router;
```

In this file, as we're using `multer` library for handling file upload. We're creating a `multer` configuration that we're storing in the variable with the name `upload`.

```js
const upload = multer({
  storage: multer.diskStorage({
    destination(req, file, cb) {
      cb(null, './files');
    },
    filename(req, file, cb) {
      cb(null, `${new Date().getTime()}_${file.originalname}`);
    }
  }),
  limits: {
    fileSize: 1000000 // max file size 1MB = 1000000 bytes
  },
  fileFilter(req, file, cb) {
    if (!file.originalname.match(/\.(jpeg|jpg|png|pdf|doc|docx|xlsx|xls)$/)) {
      return cb(
        new Error(
          'only upload files with jpg, jpeg, png, pdf, doc, docx, xslx, xls format.'
        )
      );
    }
    cb(undefined, true); // continue with upload
  }
});
```

The `multer` function takes an object as a parameter with many properties some of which are `storage` and `limits` and `fileFilter` function.

The `multer.diskStorage` function takes an object with `destination` and `filename` functions.

Here we're using ES6 function shorthand syntax so

```js
destination(req, file, cb) {
```

is same as

```js
destination: function(req, file, cb) {
```

The `destination` and `filename` function receives three input parameters namely `req(request)`, `file(actual uploaded file object)` and `cb(callback function)`.

For the callback function(cb) arguments,

- If there is an error, it will be passed as the first argument
- If there is no error, then the first argument will be null or undefined and the second argument will contain the data be passed to the callback function.

In the `destination` function, we pass the path of the folder where we will be storing the uploaded files. In our case, it will be a `files` folder inside the `server` folder.

In the `filename` function, we provide the name we want to give for each uploaded file. In our case, it will be `current_timestamp_name_of_the_file`.

For the `limits` property we specify the maximum file size allowed for the uploaded file. In our case we have provided 1MB as the max file limit.

Then inside the `fileFilter` function, we can decide to either accepts the file to be uploaded or reject it.

If the file extension matches with either `jpeg|jpg|png|pdf|doc|docx|xlsx|xls` then we allow the file to upload by calling the callback function `cb(undefined, true)` otherwise we will throw an error.

If we call `cb(undefined, false)` inside the `fileFilter` function, then the file will always be rejected and will not be uploaded.

Now, let's look at the `/upload` route

```js
Router.post(
  '/upload',
  upload.single('file'),
  async (req, res) => {
    try {
      const { title, description } = req.body;
      const { path, mimetype } = req.file;
      const file = new File({
        title,
        description,
        file_path: path,
        file_mimetype: mimetype
      });
      await file.save();
      res.send('file uploaded successfully.');
    } catch (error) {
      res.status(400).send('Error while uploading file. Try again later.');
    }
  },
  (error, req, res, next) => {
    if (error) {
      res.status(500).send(error.message);
    }
  }
);
```

Here, we're passing the `upload.single` function as the second parameter to the `/upload` route so it will act as a middleware and will be executed first before executing the function body.

Note that, the `file` parameter to the `upload.single` has to match with the name used while uploading the file in the front-end.

Remember the code we used previously for making the API call from the `App.js` file.

```js
const formData = new FormData();
formData.append('file', file);
```

we were adding the file to `formData` inside the property with the name `file`. This has to match with the `upload.single` parameter name otherwise the file upload will not work.

Inside the function, we will get the `title` and `description` inside the `req.body` and actual file inside the `req.file` object just because we've used the `multer` library.

Then we're passing those values to the object of the `File` model we created.

```js
const file = new File({
  title,
  description,
  file_path: path,
  file_mimetype: mimetype
});
```

and calling the `save` method on the object will actually save the data in the MongoDB database.

If the file type does not match with `jpeg|jpg|png|pdf|doc|docx|xlsx|xls` or the file size is larger than we mentioned (1MB) then the below code will be executed

```js
(error, req, res, next) => {
  if (error) {
    res.status(500).send(error.message);
  }
};
```

and we send back the error message to the client(our React Application).

Now, open `server/index.js` file and add the following contents inside it.

```js
const express = require('express');
const cors = require('cors');
const fileRoute = require('./routes/file');
require('./db/db');

const app = express();

app.use(cors());
app.use(fileRoute);

app.listen(3030, () => {
  console.log('server started on port 3030');
});
```

In this file, we're using `Express` server to start our Node.js application on port `3030`.

We're also using the `cors` npm package as a middleware, so we will not get a `CORS` error when we make an API call from React application running on port `3000` to the Node.js application running on port `3030`.

Now, let's run the application, to check the upload functionality.

Open `server/package.json` file and add the `start` script inside the `scripts` property.

```js
"scripts": {
  "start": "nodemon index.js"
}
```

Now, open another terminal keeping the React terminal running and execute the following command from inside the `server` folder

```js
yarn start
```

This will start our Node.js express server so we can make API calls to it.

Also start the MongoDB database server by running the following command from the terminal(If you have followed [this article](https://levelup.gitconnected.com/how-to-install-mongodb-database-on-local-environment-19a8a76f1b92?source=friends_link&sk=416b443bad1f86b292e4b72602cf5c9b) mentioned previously)

```js
./mongod --dbpath=<path_to_mongodb-data_folder>
```

So now you will have three terminals open: one for React application, one for Node.js server, and another for MongoDB server.

Let's verify the upload functionality now.

![File Upload](https://gist.github.com/myogeshchavan97/e0be7fc4c838544e2d00afeb3a82ae10/raw/0dc8da319bd7b652e2f6ac9fa0f97f58b7df8c91/file_upload.gif)

As you can see, when we upload a file, its added to the files folder, and entry is also in the MongoDB database. So file upload is successful.

But we're not showing any indication on the UI that the file is successfully uploaded. Let's do that now.

Create a new file `FilesList.js` inside the `components` folder with the following content

```js
import React, { useState, useEffect } from 'react';
import download from 'downloadjs';
import axios from 'axios';
import { API_URL } from '../utils/constants';

const FilesList = () => {
  const [filesList, setFilesList] = useState([]);
  const [errorMsg, setErrorMsg] = useState('');

  useEffect(() => {
    const getFilesList = async () => {
      try {
        const { data } = await axios.get(`${API_URL}/getAllFiles`);
        setErrorMsg('');
        setFilesList(data);
      } catch (error) {
        error.response && setErrorMsg(error.response.data);
      }
    };

    getFilesList();
  }, []);

  const downloadFile = async (id, path, mimetype) => {
    try {
      const result = await axios.get(`${API_URL}/download/${id}`, {
        responseType: 'blob'
      });
      const split = path.split('/');
      const filename = split[split.length - 1];
      setErrorMsg('');
      return download(result.data, filename, mimetype);
    } catch (error) {
      if (error.response && error.response.status === 400) {
        setErrorMsg('Error while downloading file. Try again later');
      }
    }
  };

  return (
    <div className="files-container">
      {errorMsg && <p className="errorMsg">{errorMsg}</p>}
      <table className="files-table">
        <thead>
          <tr>
            <th>Title</th>
            <th>Description</th>
            <th>Download File</th>
          </tr>
        </thead>
        <tbody>
          {filesList.length > 0 ? (
            filesList.map(
              ({ _id, title, description, file_path, file_mimetype }) => (
                <tr key={_id}>
                  <td className="file-title">{title}</td>
                  <td className="file-description">{description}</td>
                  <td>
                    <a
                      href="#/"
                      onClick={() =>
                        downloadFile(_id, file_path, file_mimetype)
                      }
                    >
                      Download
                    </a>
                  </td>
                </tr>
              )
            )
          ) : (
            <tr>
              <td colSpan={3} style={{ fontWeight: '300' }}>
                No files found. Please add some.
              </td>
            </tr>
          )}
        </tbody>
      </table>
    </div>
  );
};

export default FilesList;
```

In this file, initially inside the `useEffect` hook, we're making an API call to the `/getAllFiles` API.

The `/getAllFiles` API from `routes/file.js` looks like this:

```js
Router.get('/getAllFiles', async (req, res) => {
  try {
    const files = await File.find({});
    const sortedByCreationDate = files.sort(
      (a, b) => b.createdAt - a.createdAt
    );
    res.send(sortedByCreationDate);
  } catch (error) {
    res.status(400).send('Error while getting list of files. Try again later.');
  }
});
```

Here, we're calling the `.find` method of `mongoose` library on the `File` model to get the list of all files added in the database and then we're sorting them by the `createdAt` date in the descending order so we will get the recently added file first in the list.

Then we're assigning the result from the API to the `filesList` array in the state

```js
const { data } = await axios.get(`${API_URL}/getAllFiles`);
setErrorMsg('');
setFilesList(data);
```

Then we're using the Array map method to loop through the array and display them on the UI in a table format.

We have also added a download link inside the table. We're calling the `downloadFile` function when we click on the `download` link

```js
const downloadFile = async (id, path, mimetype) => {
  try {
    const result = await axios.get(`${API_URL}/download/${id}`, {
      responseType: 'blob'
    });
    const split = path.split('/');
    const filename = split[split.length - 1];
    setErrorMsg('');
    return download(result.data, filename, mimetype);
  } catch (error) {
    if (error.response && error.response.status === 400) {
      setErrorMsg('Error while downloading file. Try again later');
    }
  }
};
```

Inside the `downloadFile` function, we're making call to the `/download/:id` API. Note that, we're setting the `responseType` to `blob`. This is very important otherwise you will not get the file in the correct format.

The `/download` API from `routes/file.js` file looks like this:

```js
Router.get('/download/:id', async (req, res) => {
  try {
    const file = await File.findById(req.params.id);
    res.set({
      'Content-Type': file.file_mimetype
    });
    res.sendFile(path.join(__dirname, '..', file.file_path));
  } catch (error) {
    res.status(400).send('Error while downloading file. Try again later.');
  }
});
```

Here, first, we're checking if any such file exists with the provided `id`. If it exists then we're sending back the file stored in the `files` folder by setting the `content-type` of the file first.

Setting the `content-type` is very important to get the file in the correct format as we're not just uploading images but also doc, xls and pdf files. So to correctly send back the file content, the `content-type` is required.

Once we got the response from the `/download` API inside the `downloadFile` function, we're calling the `download` function provided by the [downloadjs](https://www.npmjs.com/package/downloadjs) npm library. 

`downloadjs` is a very popular library for downloading any type of file. You just have to provide the file content, its content type and name of the file you want the file to have while downloading and it will trigger the download functionality of the browser.

Now, open `router/AppRouter.js` file and add a route for the `FilesList` component.

Your `AppRouter.js` file will look like this now:

```js
import React from 'react';
import { BrowserRouter, Switch, Route } from 'react-router-dom';
import App from '../components/App';
import Header from '../components/Header';
import FilesList from '../components/FilesList';

const AppRouter = () => (
  <BrowserRouter>
    <div className="container">
      <Header />
      <div className="main-content">
        <Switch>
          <Route component={App} path="/" exact={true} />
          <Route component={FilesList} path="/list" />
        </Switch>
      </div>
    </div>
  </BrowserRouter>
);

export default AppRouter;
```

Now, open `src/App.js` and inside the `handleOnSubmit` handler after calling the `/upload` API, add a statement to redirect the user to the `FilesList` component

```js
await axios.post(`${API_URL}/upload`, formData, {
  headers: {
    'Content-Type': 'multipart/form-data'
  }
});
props.history.push('/list'); // add this line
```

So now, once the file is uploaded, we will be redirected to the `FilesList` component where we will see the list of files uploaded.

If there is some error while uploading the file, you will see the error message on the UI and you will not be redirected to the list page.

Assuming that, you have executed the `yarn start` command in two separate terminals for starting React and Node.js application and another terminal for running the MongoDB server. Now, let's verify the application functionality.

### Uploading Image File Demo

![Image Upload](https://gist.github.com/myogeshchavan97/e0be7fc4c838544e2d00afeb3a82ae10/raw/0dc8da319bd7b652e2f6ac9fa0f97f58b7df8c91/image_upload.gif)

### Uploading PDF File Demo

![PDF Upload](https://gist.github.com/myogeshchavan97/e0be7fc4c838544e2d00afeb3a82ae10/raw/0dc8da319bd7b652e2f6ac9fa0f97f58b7df8c91/pdf_upload.gif)

## Uploading Excel File Demo

![Excel Upload](https://gist.github.com/myogeshchavan97/e0be7fc4c838544e2d00afeb3a82ae10/raw/0dc8da319bd7b652e2f6ac9fa0f97f58b7df8c91/excel_upload.gif)

## Uploading Doc file Demo

![Doc Upload](https://gist.github.com/myogeshchavan97/e0be7fc4c838544e2d00afeb3a82ae10/raw/a2d11a4330152a1a3c25b7dbd79ac67278a91a5e/doc_upload.gif)

## Uploading un-supported File Demo

![Wrong file Upload](https://gist.github.com/myogeshchavan97/e0be7fc4c838544e2d00afeb3a82ae10/raw/0dc8da319bd7b652e2f6ac9fa0f97f58b7df8c91/wrong_format.gif)

As you can see, we're able to successfully upload and download any type of file which is in our supported format list.

## Removing the Need of CORS

As previously mentioned, to stop getting CORS error while calling API from React App to Node.js App, we're using `cors` library at the server-side like this:

```js
app.use(cors());
```

Try removing this line from the file and you will see that the API calls from React to Node.js fail.

![CORS Error](https://gist.github.com/myogeshchavan97/e0be7fc4c838544e2d00afeb3a82ae10/raw/2a37b60c518d4fd33f80131e01b204433b49a9a9/cors_error.png) 

To prevent this error we're using the cors middleware. But because of this, anyone in the world can access our APIs directly from their app which is not good for security reasons. 

So to remove the need of cors, we will run the Node.js and React application on the same port which will also remove the need for running two separate commands.

So First, remove the use of `cors` from `server/index.js` file and also remove the `require` statement of `cors`.

Then add the following code before the `app.use(fileRoute)` statement.

```js
app.use(express.static(path.join(__dirname, '..', 'build')));
```

Here, we're telling express to serve the contents of the build folder statically.

The build folder will be created when we run `yarn build` command for our React App.

To learn in details about how this actually work, check out my previous article [here](https://levelup.gitconnected.com/how-to-render-react-app-using-express-server-in-node-js-a428ec4dfe2b?source=friends_link&sk=3f152ac7908f540b209f07f683b494cd)

and import the `path` Node.js package at the top of the file.

```js
const path = require('path');
```

Your `server/index.js` file will look like this now:

```js
const express = require('express');
const path = require('path');
const fileRoute = require('./routes/file');
require('./db/db');

const app = express();

app.use(express.static(path.join(__dirname, '..', 'build')));
app.use(fileRoute);

app.listen(3030, () => {
  console.log('server started on port 3030');
});
```

Now, open the main `package.json` file add `start-app` script in the `scripts` section.

```js
"scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test",
    "eject": "react-scripts eject",
    "start-app": "yarn build && (cd server && yarn start)"
  },
``` 

Now, Assuming you have already started the MongoDB server, you just need to run `yarn run start-app` command from the terminal.

This command will create a `build` folder which will contain all of our React application and then starts our Node.js server on port `3030`.

So now, we can access our React and Node.js application on the same `3030` port. So there is no need of running two separate commands and you can access the application at http://localhost:3030/

But there is one issue, If you refresh the `/list` page, you will get a 404 error. This is because we're starting the App using Express server so when we hit the `/list` route, it will go to the server to check for that route.

But the server does not contain such a route but our React App has that route so to fix this we need to add some code.

Open `server/index.js` file and before the `app.listen` call, add the following code.

```js
app.get('*', (req, res) => {
  res.sendFile(path.join(__dirname, '..', 'build', 'index.html'));
});
```

The above code will send the `build/index.html` file back to our React application when we hit any route which is not present on the server-side. 

So as the `/list` route is not present on the server side, out React app will handle that routing as we're redirecting the user to the `index.html` file.

So make sure the above line of code is added after all your server-side routes because the `*` in `app.get` will match any route.

Your final `server/index.js` file will look like this now:

```js
const express = require('express');
const path = require('path');
const fileRoute = require('./routes/file');
require('./db/db');

const app = express();

app.use(express.static(path.join(__dirname, '..', 'build')));
app.use(fileRoute);

app.get('*', (req, res) => {
  res.sendFile(path.join(__dirname, '..', 'build', 'index.html'));
});

app.listen(3030, () => {
  console.log('server started on port 3030');
});
```

Now, restart your application by running `yarn run start-app` command and now refreshing the `/list` route will not give you a 404 error.

## Conclusion

We have now finished creating the complete file upload and download functionality using MERN stack. 

You can find the complete source code for this application in [this repository](https://github.com/myogeshchavan97/react-upload-download-files).

**Don't forget to subscribe to get my weekly newsletter with amazing tips, tricks and articles directly in your inbox [here](https://yogeshchavan.dev).**

[<img src="https://cdn.buymeacoffee.com/buttons/default-yellow.png" >](https://www.buymeacoffee.com/myogeshchavan97)