How to Upload Images to a Server with React and Express ๐ŸŒƒโš›๏ธ

Picture of man on hill in Big Sur, with clouds and ocean in background.

Uploading images to an Express server with React is not as hard as it sounds! We are going to go through this tutorial to upload to a local directory on our computer through our local server, but this method can be used in online servers, for example cPanel, to upload files to your remote server, and the process is almost identical (Scroll to the end of this post to see some cPanel server uploading tips). I recorded a quick 30-minute walkthrough video as well which can be used to supplement the instructions in this blog post! ๐ŸŽฅ

Diving In ๐Ÿคฟ

We start by creating a React app using npm create-react-app. Then we can go in and delete all of the extra code from App.js., add an h1, and we’re left with a file that looks like this:

import './App.css';

function App() {
  return (
    <div className="App">
      <h1>Image Upload Tutorial</h1>
    </div>
  );
}

export default App;

Let’s get started on our server code as well. We’re going to create a new folder for the server file in our project folder. So in my React app folder titled ‘image-uploads-tutorial’, I created a folder called ‘server’. In this folder we’re going to create a file called app.js, which will be our server file (not to be confused with the capitalized React file, App.js). After we create app.js, we will open a terminal, CD (change directory) into our ‘server’ directory, and initialize the server directory with the command npm init. Make sure that the ‘entry point’ option is app.js, not index.js. If it shows index.js as the default, just type app.js and press ‘enter’.

After initializing the server folder, we will install the dependencies that we will use in the server directory. We will be using Express, Body-Parser, Cors, and an image upload handling package called Multer. We’re using Body-Parser to help us read test values that we will send to the server, and Cors just to avoid potential blocks in the browser, that prevent communication between servers. So we run npm i expresss body-parser cors multer to get all of these packages installed at once. Then we can code in these dependencies to our app.js, and in addition, we’ll add listening code for port 4000:

const express = require('express');
const app = express();
const cors = require("cors");
const multer = require('multer');
const bodyParser = require('body-parser');

const port = 4000;
app.listen(port, process.env.IP, function(){
  console.log(`Server is running on port ${port}`);
});

We want to add an Express POST route to our NodeJS app. We will write it as follows, including a console.log to display in our server terminal, and a res.send to send a success message back to the React app frontend:

const express = require('express');
const app = express();
const cors = require('cors');
const multer = require('multer');


//ADD EXPRESSS ROUTE
app.post('/image-upload', (req, res) => {
  console.log('POST request received to /image-upload.')
})

const port = 4000;
app.listen(port, process.env.IP, function(){
  console.log(`Server is running on port ${port}`);
  res.send('POST request recieved on server to /image-upload.');

});

Adding Functionality โš™๏ธ

We now are going to create a button to make a POST request in our React app. This will allow us to test the connection to our server, and then to upload the image. We will use Axios to make our POST request in React, so we will now install Axios in React. We need to CD into our React app, which can be done in the VSCode terminal (accessed with ctrl+` ). Usually I install React packages and run npm start in the VSCode terminal, while I use a seperate Windows terminal to run my server. However you can run multiple terminals in VSCode by pressing the “+” for a new terminal (or Ctrl+Shift+` ).

After we install Axios, we will import it in our React app, and in addition we will create a button in our React app with an onClick event listener. We will write a function called handleClick(), and add this to our onClick listener. We will also add a console.log to handleClick to make sure that it’s connected to the button:

import './App.css';
import axios from 'axios';

function App() {
  const handleClick = () => {
    console.log('handleClick working!')
  }
  return (
    <div className="App">
      <h1>Image Upload Tutorial</h1>
      <button onClick={handleClick}>Upload!</button>
    </div>
  );
}

export default App;

Now our React app should look like the image below, and we should get a “handleClick working!” message in the console when we click our button, as shown below:

Screenshot of React app.

Great! Our button works. We are now going to add an Axios request to our server in our React app. This will allow us to test the connection of our React app to our server. We will add the following Axios request to our ‘image-upload’ route:

import './App.css';
import axios from 'axios';

function App() {
  const handleClick = () => {
    // console.log('handleClick working!') WE CAN REMOVE THIS NOW
    axios.post('http://localhost:4000/image-upload', { //ADD AXIOS POST REQUEST 
      value: 'This is my awesome test value!'
    })
  }
  return (
    <div className="App">
      <h1>Image Upload Tutorial</h1>
      <button onClick={handleClick}>Upload!</button>
    </div>
  );
}

export default App;

If we click our button now to make our POST request, we will get a CORS error in the console (shown below), so we need to add our CORS code to our server app.

Screenshot of React CORS error.
CORS error message

We will add our CORS code with our path as http://localhost:3000:

const express = require('express');
const app = express();
const cors = require("cors");
const multer = require('multer');

//ADD CORS CODE: 
const corsOrigin = 'http://localhost:3000';
app.use(cors({
  origin:[corsOrigin],
  methods:['GET','POST'],
  credentials: true 
})); 

app.post('/image-upload', (req, res) => {
  console.log('POST request received to /image-upload.')
;
  res.send('POST request recieved on server to /image-upload.');
})

const port = 4000;
app.listen(port, process.env.IP, function(){
  console.log(`Server is running on port ${port}`);
});

We can start up our app.js in our second terminal now, by using node app.js, or by using the convenient Nodemon package, which refreshes the server upon saving. Nodemon can be easily installed with npm i nodemon and used to start the server with nodemon app.js. Now when we hit our “Upload!” button, we should receive a success message in both the server terminal and the browser console:

Screenshot of React server connection.

Fantastic! ๐Ÿ˜ƒ We will now add a console.log(req.body) to our server code, to make sure our test value is going through. We will need to add body-parser code as well, to read the POST request body:

const express = require('express');
const app = express();
const cors = require("cors");
const multer = require('multer');
const bodyParser = require('body-parser');

//ADD BODY-PARSER CODE
app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json());

const corsOrigin = 'http://localhost:3000';
app.use(cors({
  origin:[corsOrigin],
  methods:['GET','POST'],
  credentials: true 
})); 

app.post('/image-upload', (req, res) => {
  console.log('POST request received to /image-upload.');
  //ADD REQ.BODY FOR TEST VALUE
  console.log('Axios POST body: ', req.body);
 
  res.send('POST request recieved on server to /image-upload.');
})

const port = 4000;
app.listen(port, process.env.IP, function(){
  console.log(`Server is running on port ${port}`);
});

When we press “Upload!”, our test value should display in our server console:

Screenshot of server test value.

Adding our Image File Upload Functionality ๐Ÿ–๏ธ

Awesome! ๐Ÿ˜ƒ Now that our app and server are connected, we will add a file input into our React app, to allow us to upload our image. We will also add an onChange event listener, to check for when a file has been uploaded. This will allow us to retrieve the image file information from the input element, send that info to a function, and store the info in React state. We will add state in a moment, for now we will just create a file input and connect it to a function, handleFileUpload() :

import './App.css';
import axios from 'axios';

function App() {
  const handleClick = () => {
    axios.post('http://localhost:4000/image-upload', {
      value: 'This is my awesome test value!'
    })
    .then(res => {
      //WE CAN REMOVE THIS NEXT LINE
      console.log('Axios POST request successful!') 
      console.log('Axios response: ', res)
    })
  }
  //OUR FILE INPUT HANDLER
  const handleFileInput = () => {
    console.log('handleFileInput working!')
  }
  return (
    <div className="App">
      <h1>Image Upload Tutorial</h1>
      <button onClick={handleClick}>Upload!</button>
      {/* OUR FILE INPUT ELEMENT */}
      <input type="file" onChange={handleFileInput}/>
    </div>
  );
}

export default App;

Now, we can upload an image to our React app, and we should get a message in the browser console after we do:

React app file input success screenshot.

We will now add state to our React app for storing our image data. We will do this using the useState hook, so we will also need to import useState from our React package. Our state will be called image, and we will set it using setImage:

import './App.css';
import axios from 'axios';
//IMPORT USESTATE
import React, { useState } from 'react';

function App() {
  //ADD USESTATE
  const [image, setImage] = useState(null);
  const handleClick = () => {
//...

Now that we have our state, we will use the event from our input change to retrieve our file info. If that sounds confusing, it’s not! All we need to do is pass the event argument, e, to handleFileInput(), and we can use it to retrieve our file info. We get our file info from our file input element using e.target.files[0]. Then we will use an interface called FormData(), which will allow us to send our file info to the server correctly. We will append 3 things to the FormData: a chosen name (we will use “my-image-file”), our file info, and a name from the file data to the form data, and use setImage() to save our form data in our state – in a package ready to be shipped to the server! ๐Ÿ“ฆ All of this is done in only a few lines of code:

//...
  const getFileInfo = (e) => {
 //NOTE THE ADDITION OF 'e' PARAMETER
    console.log('File info working!')
    console.log(e.target.files[0]);
    const formData = new FormData(); 
    //FILE INFO NAME WILL BE "my-image-file"
    formData.append('my-image-file', e.target.files[0], e.target.files[0].name);
    setImage(formData);

  }
//...

One cool thing: Notice how we don’t need to pass in e to the event handler function in the event listener, in our input element! i.e. we dont need:

//WE DONT NEED THIS:
<input type="file" onChange={(e) => {getFileInfo(e)}}></input>

//ALL WE NEED IS THIS <3 :
<input type="file" onChange={getFileInfo}></input>

That’s a cool trick I learned from watching Dan Abramov’s React Conf 2018 Hooks release talk ๐Ÿ˜ƒ. Anyway, now when we upload our image file, we will get the data shown in our console from our console.log(e.target.files[0]). Note that this is however not the same as our FormData() in our image state.

React form data screenshot.

Send It! Sending our Image File to the Server, and Finishing Up ๐Ÿ

Now that we have our file data in image, we will send it to our server in our Axios POST request. We’re almost there! We will replace our test value object parameter in our Axios request with our image state variable:

//...
  const handleClick = () => {
    //REPLACE TEST VALUE WITH 'image'
    axios.post('http://localhost:4000/image-upload', image)
    .then(res => {
      console.log('Axios response: ', res)
    })
  }
//...

We will also update our server code to include our Multer engine, which will handle the file information. The engine will be passed as a parameter into our app.post. We also need to create a folder where we want our files to be saved. The folder can be called “uploaded_images”. This folder can be in our project folder, or anywhere else on the computer. We will then add the path to this folder into our server code as a variable. If we are uploading to a live server, the path will look something like “home/public_html/uploaded_images”. On our local machine, the path will look more like this: “C:/Users/User-Name/Documents/Coding/image-upload-tutorial/uploaded_files”. The Multer engine is taken from the Multer docs on NPM, and can be seen in the server code below:


//...
const imageUploadPath = 'C:/Users/User-Name/Documents/Coding/image-upload-tutorial/uploaded_files';

const storage = multer.diskStorage({
  destination: function(req, file, cb) {
    cb(null, imageUploadPath)
  },
  filename: function(req, file, cb) {
    cb(null, `${file.fieldname}_dateVal_${Date.now()}_${file.originalname}`)
  }
})

const imageUpload = multer({storage: storage})

app.post('/image-upload', (req, res) => {
//...

In Line 10 above, we are defining the name for our image file. We can use Date.now() as a way to return a unique numerical value and avoid image name duplication. We will add our variable imageUpload with .array() as an argument in app.post. We need to pass our chosen file info name, “my-image-file”, into the .array(). This will allow Multer to read the respective file data:


//...
//PASS IN imageUpload.array("my-image-file)
app.post('/image-upload', imageUpload.array("my-image-file"), (req, res) => {
  console.log('POST request received to /image-upload.');
  console.log('Axios POST body: ', req.body);
  res.send('POST request recieved on server to /image-upload.');
})
//...

Our final code for our server app should look like this:

const express = require('express');
const app = express();
const cors = require("cors");
const multer = require('multer');
const bodyParser = require('body-parser');

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

const corsOrigin = 'http://localhost:3000';
app.use(cors({
  origin:[corsOrigin],
  methods:['GET','POST'],
  credentials: true 
})); 

const imageUploadPath = 'C:/Users/User-Name/Documents/Coding/image-uploads-tutorial-blog-walkthrough/uploaded_files';

const storage = multer.diskStorage({
  destination: function(req, file, cb) {
    cb(null, imageUploadPath)
  },
  filename: function(req, file, cb) {
    cb(null, `${file.fieldname}_dateVal_${Date.now()}_${file.originalname}`)
  }
})

const imageUpload = multer({storage: storage})

app.post('/image-upload', imageUpload.array("my-image-file"), (req, res) => {
  console.log('POST request received to /image-upload.');
  console.log('Axios POST body: ', req.body);
  res.send('POST request recieved on server to /image-upload.');
})

const port = 4000;
app.listen(port, process.env.IP, function(){
  console.log(`Server is running on port ${port}`);
});

And our final React app code will look like this:

import './App.css';
import axios from 'axios';
import React, { useState } from 'react';

function App() {
  const [image, setImage] = useState(null);
  const handleClick = () => {
    axios.post('http://localhost:4000/image-upload', image)
    .then(res => {
      console.log('Axios response: ', res)
    })
  }
  const handleFileInput = (e) => {
    console.log('handleFileInput working!')
    console.log(e.target.files[0]);
    const formData = new FormData(); 
    formData.append('my-image-file', e.target.files[0], e.target.files[0].name);
    setImage(formData);
  }
  return (
    <div className="App">
      <h1>Image Upload Tutorial</h1>
      <button onClick={handleClick}>Upload!</button>
      <input type="file" onChange={handleFileInput}/>
    </div>
  );
}

export default App;

And hopefully, this time when we press “Upload!”, we will shortly thereafter see a brand new little image file in our “uploaded_files” folder!

Photo of files and folders.

If this worked, then congratulations! You just did something awesome! You were able to upload an image to a server, and then save it to your directory! Great job. The process to uploading to a remote server is almost identical, with a couple of tweaks. I go though it a little below. Thanks for joining me in this tutorial, and joining me in appreciating the awesome power of technology. I hope you have a very successful career or hobby in Web Development. ๐Ÿ’œ

Bonus: Tips for Uploading to cPanel Server

This tutorial did upload in a local folder, but the process is almost identical for uploading to a server. When I did this for my Node app on my cPanel server, the main difference was that the path name was something like:

“/public_html/home/uploaded_files”

And my post route in my React app was something like:

“https://omarshishani.com/api/image-upload”

But the rest was remarkably identical! One little tricky trick that I had to use for my cPanel server: I needed to create an empty folder for each different route in my express app, with the same name and structure as the route (for example “image-uploads/images” would need two folders: “image-uploads > images”). These were stored in the same folder that my React build files were in, since in the cPanel Node app wizard I set my Node “Application URL” to be “https://omarshishani.com/image-upload-tutorial”. So I would have my project folder on my server: “react-image-upload-tutorial”, and then inside of that I would have my empty route folders: “api > image-upload”

Cpanel folder structure.
cPanel server folder structure

I hope you have a great day! ๐Ÿ˜Š