How to start upload file with React and Nodejs
In this tutorial, we go through building a full-stack file upload solution with express and React.js
we gonna create a basic form for handle file upload with React and server-side with Nodejs this tutorial is so simple you can follow along with 5 minutes it should be done if you think TLDR; just check finish code here and live demo will host on Codesandbox
Setting up the Node server
If you’re following for the full tutorial, the complete server code is available. Go to server directory, open terminal and execute npm install
and node index
, this will run the server. Then continue from the React section.
- Create a directory upload-server, open terminal in this directory and run
npm init
. Accept all defaults. - In the same terminal, install upload and cors package will command
npm express express-fileupload cors
start on index.js is the entry file.
Here’s the Initial code for the server.
const PORT = 5000;
const express = require('express');
const cors = require('cors');const app = express();
app.use(cors());
app.get('/', (req, res) => {
return res.status(200).send("It's working");
});app.listen(PORT, () => {
console.log('Server Running sucessfully.');
});
Now, go to your browser at localhost:5000 and you should see It’s working.
Upload Endpoint
Let’s define /upload at the upload endpoint. It needs the middleware fileUpload, set this thing first.
app.use(
fileUpload({
useTempFiles: true,
safeFileNames: true,
preserveExtension: true,
tempFileDir: `${__dirname}/public/files/temp`
})
);
It accepts many options, which you can find here.
The temporary directory is used to store files temporarily instead of using computer memory(RAM). This is very useful in uploading large files.
Safe File removes non-alphanumeric characters from except dash and underscores from the filename. You can also define regex instead of true for custom names.
preserveExtension preserves the extension of the file name, this is false by default. It also supports customizations, refer to the documentation for options.
app.post('/upload', (req, res, next) => {
let uploadFile = req.files.file;
const name = uploadFile.name;
const md5 = uploadFile.md5();
const saveAs = `${md5}_${name}`;
uploadFile.mv(`${__dirname}/public/files/${saveAs}`, function(err) {
if (err) {
return res.status(500).send(err);
}
return res.status(200).json({ status: 'uploaded', name, saveAs });
});
});
The file in req.files.file refers to file as a name in the input field. That is NAME in both <input name="NAME" type="file" />
and req.file.NAME
should be the same.
To prevent overwriting the files, I used an md5 function from the uploadFile package and saved the file as md5_filename.ext.
You can test the API using Postman as well.
Send a post request to /upload with a file. It should successfully go through.
React Front End
A server is waiting for files, you need an interface to allow users to input the files. Let’s create an interface using React.
Set up the react application with create-react-app.
npx create-react-app upload-front
Put a form with input type file in App.js. It should look like the following:
import React, { Component } from 'react';class App extends Component {
render() {
return (
<form className="App">
<input type="file" name="file" />
<button>Upload</button>
</form>
);
}
}export default App;
The button won’t upload by itself. Define a few methods and states to achieve functionality.
State: selectedFile
Methods: handleFileChange, and handleUpload
Add on change listener as handleFileChange on input. You can attach handleFileUpload as a form submit an action or as click listener on a button. We’ll use the state to hold file so it doesn’t matter.
Here’s how it looks.
import React, { Component } from 'react';class App extends Component {
handleFileChange = () => {
// define file change
};
handleUpload = () => {
// define upload
};
render() {
return (
<form className="App" onSubmit={handleUpload}>
<input type="file" name="file" onChange={handleFileChange} />
<button onClick={handleUpload}>Upload</button>
</form>
);
}
}export default App;
Do not keep handleUpload on both onClick and onSubmit.One is enough.
Handling the file change, we keep track of file and an upload button click.
Handling file change
handleFileChange = (event) => {
this.setState({
selectedFile: event.target.files[0]
});
};
The file input is an event for the click and files are stored in an array (even in single file upload).
Handling the Upload
To handle the uploading, we’ll use Axios to send ajax request. Install and import Axios.
npm i axios
import axios from 'axios
handleUpload = (event) => {
event.preventDefault();
const data = new FormData();
data.append('file', this.state.selectedFile, this.state.selectedFile.name);
axios.post(endpoint, data).then((res) => {
console.log(res.statusText);
});
};
A file is sent as FormData. It just logs the response text after completing the AJAX request.
While it looks too ugly, uploading should start working now.
Loading State
While the file is uploaded successfully, the user doesn’t see the response. Showing the response after the completion is quite simple. Let’s show the progress instead.
axios can take some parameters with the post method. One of them is ProgressEvent.
{
onUploadProgress: ProgressEvent => {
this.setState({
loaded: (ProgressEvent.loaded / ProgressEvent.total*100),
}
This sets a loaded state to whatever percentage is completed.
We also need to display it, so wrap it in a paragraph outside the form. This will crash the application as JSX can’t return multiple parts. Wrap the return in React Fragment.
Here’s the code:
return (
<React.Fragment>
<form className="App" onSubmit={this.handleUpload}>
<input
className="inputfile"
id="file"
type="file"
name="file"
onChange={this.handleFileChange}
/>
<label htmlFor="file">Choose a file</label>
<button onClick={this.handleUpload}>Upload</button>
</form>
<p>{Math.round(this.state.loaded)}%</p>
</React.Fragment>
);
I’ve given it a label, that we’ll play with soon. Math.round is to avoid decimal values.
But this will throw error initially as the state loaded doesn’t even exist.
Initialize the state on a component mount with:
state = { selectedFile: null, loaded: null };
There’s no need of defining a constructor for a state, if you’ve been doing that way.
Here’s how handleUpload is now…
handleUpload = (event) => {
// define upload
event.preventDefault();
const data = new FormData();
data.append('file', this.state.selectedFile, this.state.selectedFile.name);
axios
.post(endpoint, data, {
onUploadProgress: (ProgressEvent) => {
this.setState({
loaded: (ProgressEvent.loaded / ProgressEvent.total) * 100
});
}
})
.then((res) => {
console.log(res.statusText);
});
};
Unuglifying things…
This is how ugly it is right now…
I grabbed CSS from here and moved to index.css.
I also added states uploading, message, and defaultMessage. If it is currently uploading, the loaded state will show the % completed. Otherwise, the message will be shown.
The JSX returned has also changed slightly, the react part and methods remain the same. This comes from that same HTML file.
return (
<form className="box" onSubmit={this.handleUpload}>
<input
type="file"
name="file-5[]"
id="file-5"
className="inputfile inputfile-4"
onChange={this.handleFileChange}
/>
<label htmlFor="file-5">
<figure>
<svg
xmlns="http://www.w3.org/2000/svg"
width="20"
height="17"
viewBox="0 0 20 17"
>
<path d="M10 0l-5.2 4.9h3.3v5.1h3.8v-5.1h3.3l-5.2-4.9zm9.3 11.5l-3.2-2.1h-2l3.4 2.6h-3.5c-.1 0-.2.1-.2.1l-.8 2.3h-6l-.8-2.2c-.1-.1-.1-.2-.2-.2h-3.6l3.4-2.6h-2l-3.2 2.1c-.4.3-.7 1-.6 1.5l.6 3.1c.1.5.7.9 1.2.9h16.3c.6 0 1.1-.4 1.3-.9l.6-3.1c.1-.5-.2-1.2-.7-1.5z" />
</svg>
</figure>
<span>
{this.state.uploading
? this.state.loaded + '%'
: this.state.message}
</span>
</label>
<button className="submit" onClick={this.handleUpload}>
Upload
</button>
</form>
);
After adding some CSS for the button as well, it looks like:
If you click on the upload button without choosing a file, it’ll crash.
This is because the selected file is null, and null doesn’t have the property of name.
To fix this, return from the handleUpload method if the selectedFile is null. Also, set the message to upload first so the user gets some feedback on what to do.
handleUpload = (event) => {
event.preventDefault();
if (this.state.uploading) return;
if (!this.state.selectedFile) {
this.setState({ message: 'Select a file first' });
return;
}
this.setState({ uploading: true });
const data = new FormData();
data.append('file', this.state.selectedFile, this.state.selectedFile.name);
axios
.post(endpoint, data, {
onUploadProgress: (ProgressEvent) => {
this.setState({
loaded: Math.round(
(ProgressEvent.loaded / ProgressEvent.total) * 100
)
});
}
})
.then((res) => {
this.setState({
selectedFile: null,
message: 'Uploaded successfully',
uploading: false
});
console.log(res.statusText);
});
};
Also, I’ve rounded the loaded state itself instead of rounding in the output. If you click upload without selecting the file, the message will change to select a file first.
You can upload another file before the previous one has finished. We can deal with this by using the unloading state. Just return from the handle upload without uploading if the uploading is true.
if (this.state.uploading) return;
if (!this.state.selectedFile) {
this.setState({ message: 'Select a file first' });
return;
}
this.setState({ uploading: true });
After successfully uploading, it changes the message to Uploaded Successfully while uploading state changes to false.
handleFileChange is modified to show the file name that’s going to upload.
handleFileChange = (event) => {
this.setState({
selectedFile: event.target.files[0],
loaded: 0,
message: event.target.files[0]
? event.target.files[0].name
: this.state.defaultmessage
});
};
Error Handling
If you shut down the server, this application will still try to upload the file and loaded will change to NaN (Not a Number). Since uploading was set true, you can’t upload even the server comes back as handleUpload is blocked by this.
Add a catch to the post request..
.catch((err) => {
this.setState({
uploading: false,
message: 'Failed to upload'
});
});
Awesome, it uploads a file while displaying the message on error and success.
Conclusion
your learn how to basic upload with Reactjs and Nodejs in an easy way to continue next step you can implement validation on client and server or show preview before upload