How to Upload Images to a Server with React and Express 🌃⚛️

Omar Shishani
11 min readDec 16, 2020

--

Uploading images to an Express server with React is not as hard as it sounds! We are going to go through this method for a local server, but this method can be used in online servers, for example cPanel, to upload files to your remote server. 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 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;

React JSX

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 this, we will install the dependencies that we will use while still in the server directory. We will be using Express, Body-Parser, Cors, and an image upload handling package called Multer. We're using 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 add the dependencies to our app.js code, and in addition, we'll listen on 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}`);
});

JavaScript

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.');
});

JavaScript

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. Usually I install React packages and run npm start in the VSCode terminal, while I use a seperate Windows terminal to run my server.

After we install Axios, we will import it in our React app, and in addition we will create a button in our React app, and add an onClick event listener to our button. Then we will write a function called handleClick(), and add this to our onClick event listener. We will 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;

React JSX

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:

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 reqest 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;

React JSX

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.

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}`);
});

React JSX

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:

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}`);
});

JavaScript

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

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;

React JSX

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

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 = () => {
//...

React JSX

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 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);
}
//...

React JSX

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>

React JSX

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:

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)
})
}
//...

React JSX

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) => {
//...

JavaScript

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, to avoid image name duplication. We will now 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.');
})
//...

JavaScript

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}`);
});

JavaScript

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;

React JSX

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

If this worked, then congratulations! You just did something awesome! You were able to upload a whole image to a server! Great job. Thanks for joining me in this tutorial, and 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 server folder structure

I hope you have a great day! 😊

--

--

Omar Shishani

Hi! I am a React developer who loves technology and learning new things in all different fields. https://omarshishani.com