React Node JS File Upload - Do It Right From the Start
In this post we will look on React and NodeJS file upload and how to bind them properly.
Really often we need to upload some files from our React application and we want to upload them to our server which is typically build with NodeJS because we want to store these files on the backend. The question is how to do that efficiently.
Initial project
Here I already prepared for us 2 folders: backend
and frontend
.
// backend/src/index.JS
const express = require('express')
const cors = require('cors')
const app = express()
app.use(express.json())
app.use(express.urlencoded({extended: true}))
app.use(cors())
app.listen(3000, () => {
console.log('Server is running')
})
Here is our server with plain Express application without any logic. We only added here a cors
package so we can make requests from React project to our API.
Additionally we have a frontend application which is a React application generated with Vite.
const App = () => {
return (
<div>
<form>
<input type="file" multiple />
<button type="submit">Upload files</button>
</form>
</div>
)
}
Here we have just an input to upload multiple files and a button to submit them.
Here is how it looks in browser.
Frontend implementation
First let's apply changes to our client side. We want to store files that we upload inside the state so we can upload them later.
const App = () => {
const [files, setFiles] = useState([]);
const changeFiles = (e) => {
setFiles(e.target.files);
};
return (
<div>
<form>
<input type="file" multiple onChange={changeFiles} />
<button type="submit">Upload files</button>
</form>
</div>
)
}
Here we created a state files
for all items that we upload and we can use them later.
const App = () => {
const [files, setFiles] = useState([]);
const changeFiles = (e) => {
setFiles(e.target.files);
};
const uploadFiles = (e) => {
e.preventDefault();
console.log(files)
};
return (
<div>
<form onSubmit={uploadFiles}>
<input type="file" multiple onChange={changeFiles} />
<button type="submit">Upload files</button>
</form>
</div>
)
}
We added an onSubmit
on our form and logged our list of files for upload.
As you can see we can now access a list of prepared files.
Preparing files
The next question is how we will implement files uploading on the backend. The most popular package to upload files in NodeJS is called Multer.
Multer is a NodeJS middleware to handle multipart/form-data, which is primary used for uploading files.
But most importantly in order for it to work we must send all our files in the specific format as a formData
.
const uploadFiles = (e) => {
e.preventDefault();
const formData = new FormData();
for (const file of files) {
formData.append("photos", file);
}
axios
.post("http://localhost:3000/files", formData, {
headers: {
"Content-Type": "multipart/form-data",
},
})
.then((response) => {
console.log("response", response.data);
});
};
Here we created a formData
property and appened there a list of files in photos
property. It is important that we append all our data in a single key as exactly this photos
key is what we will read on the backend.
After this we are doing an API call to our backend with FormData
. Most importantly we set our headers to multipart/form-data
so backend understands what we send.
As you can see after we submit our files our request flies to API but obviously we didn't implement it yet.
Configuring Multer
Now we must add Multer configuration to be able to access our files.
const multer = require("multer");
const upload = multer({ dest: 'uploads/' });
...
app.post("/files", upload.array("photos"), (req, res) => {
res.json({ files: req.files });
});
Here we added multer
, created an upload
property which stores the destination of our files and added upload.array
middleware to our request. Most important part is that we use the same key photos
like in the client.
As you can see our request was successful, our files were saved and we got a response with all urls back on the client. We get here a destination, encoding, filename, mimetype, size and others.
Our files were uploaded but they don't have any extension but just unique names. It is not the way how I would like to have them.
const storage = multer.diskStorage({
destination: (req, file, cb) => {
cb(null, process.cwd() + "/uploads");
},
filename: (req, file, cb) => {
cb(null, Date.now() + path.extname(file.originalname));
},
});
const upload = multer({ storage });
In order to tune that we must do storage
ourselves by calling diskStorage
. Inside we provide a destination
and a filename
. Our destination is /uploads
but for filename we merge a date with the extension of the original name.
Now if we try to upload files it will create them with unique names and the correct extension.
Accessing files
The next thing that we need is to allow our frontend to access our files from the backend. As of now uploads
folder is not binded to any URL which our client can access.
app.use(express.static(process.cwd() + "/uploads"));
This is enough to serve our uploads
folder as static
As you can see now we can access our files directly in the root.
Rendering images
The last thing that I want to do inside our client is to render images after we got them back from the backend. It makes a lot of sense to show user what we uploaded on the screen.
const App = () => {
...
const [images, setImages] = useState([]);
const uploadFiles = (e) => {
...
axios
.post("http://localhost:3000/files", formData, {
headers: {
"Content-Type": "multipart/form-data",
},
})
.then((response) => {
console.log("response", response.data);
const filenames = response.data.files.map((file) => file.filename);
setImages(filenames);
});
};
return (
<div>
...
<div>
{images.map((image) => (
<img key={image} src={`http://localhost:3000/${image}`} />
))}
</div>
</div>
);
};
Here we created a new state images
to store filenames that we want to render. We collect names when our files are uploaded and store them in state to render on the screen.
As you can see we successfully uploaded files to the server and rendered them on the screen.
Want to conquer your next JavaScript interview? Download my FREE PDF - Pass Your JS Interview with Confidence and start preparing for success today!
📚 Source code of what we've done