Handling FormData on the Frontend and Backend (It’s Easier Than You Think!)

Hey Guys!!!!
As we devs already know, Working with forms is something almost every web developer faces, whether you’re building a simple contact form, a blog, or an image upload feature. But when it comes to sending files or images, like pictures or PDFs, a normal JSON request isn’t enough. That’s where FormData comes in!
In this post, I’ll walk you through how to handle FormData on both the frontend and backend, step by step. We’ll cover how to send form data (including files) from your frontend and how to receive and process it on the backend. Don’t worry if you’re just starting out — I’ll keep things simple, clear, and easy to follow. Let’s jump in!”
Before we dive into the code, let’s quickly understand what FormData is and why we need it.
When you send data from a form, like text inputs, checkboxes, or images to your backend, you usually have two main options:
✅ JSON — great for plain text and numbers, but it doesn’t handle files.
✅ FormData — perfect when you need to send files (like images, PDFs, or videos) along with text data.
FormData works by packaging your form inputs (including files) into a special format that the backend can understand, usually as multipart/form-data. This makes it easy to handle mixed content without manually converting files into base64 or doing complex tricks.
While it’s possible to send images as Base64 (embedding the image data as a long string within your JSON), I wouldn’t recommend it for most cases. Here’s one major reason:
- Increased file size: Base64 encoding increases the file size by around 33%, which means it takes up more bandwidth and memory.
In this guide, we’ll cover:
Setting up the frontend form — how to create a form that collects text and files, and how to send that data using FormData.
Handling the backend — how to receive and process that data with Express and Multer.
Getting straight to work
🚀 1. Setting Up the Frontend Form
First, let's create a simple form that collects text (like the title and content of your blog post) and a file (such as an image). We’ll use React for this example.
Frontend Code: React (BlogForm Component)
import React, { useState } from 'react';
import axios from 'axios';
function BlogForm() {
const [title, setTitle] = useState('');
const [content, setContent] = useState('');
const [image, setImage] = useState(null);
//Handle Image Changes
function handleImage(e) {
const file = e.target.files[0] || null
if(!file) {
return
}else {
setImage(file)
}
}
//Submit Form Handler
const handleSubmit = async (e) => {
e.preventDefault();
// Create FormData
const formData = new FormData();
formData.append('title', title);
formData.append('content', content);
if (image) formData.append('image', image);
try {
const res = await axios.post('http://localhost:4000/post/comments', formData, {
headers: { 'Content-Type': 'multipart/form-data' },
});
console.log('Blog post success:', res.data);
alert('Blog post submitted!');
} catch (err) {
console.error('Blog post error:', err);
alert('Failed to submit blog post.');
}
};
return (
<form onSubmit={handleSubmit}>
<input
type="text"
value={title}
onChange={(e) => setTitle(e.target.value)}
placeholder="Blog Title"
required
/>
<textarea
value={content}
onChange={(e) => setContent(e.target.value)}
placeholder="Blog Content"
required
/>
<input
type="file"
accept="image/*"
onChange={(e) => handleImage(e)}
/>
<button type="submit">Submit</button>
</form>
)
}
How it Works:
State Management: We use React’s useState to manage the values of title, content, and the selected image.
FormData: When the form is submitted, we create a new FormData object and append the title, content, and selected image file to it.
Axios Request: The formData is sent via a POST request to the backend (http://localhost:3000/blogs) with the header Content-Type: multipart/form-data
🔥 2. Handling the Backend with Express + Multer + Cloudinary
Now, let’s set up the backend to receive the FormData. We’ll use Express and Multer to handle the file upload and text fields and then upload to Cloudinary.
1. Install the Cloudinary SDK:
First, make sure you've installed the cloudinary
package via npm:
npm install express multer cloudinary
2. Backend Code: Express + Multer + Cloudinary
const express = require('express');
const multer = require('multer');
const { v2: cloudinary } = require('cloudinary'); // Import Cloudinary SDK
const cors = require('cors');
const app = express()
// Set up Cloudinary configuration with your credentials
cloudinary.config({
cloud_name: 'your-cloud-name', // Replace with your Cloudinary cloud name
api_key: 'your-api-key', // Replace with your Cloudinary API key
api_secret: 'your-api-secret', // Replace with your Cloudinary API secret
});
// Enable CORS for frontend connection
app.use(cors());
const port = 4000;
const storage = multer.diskStorage({
destination: function (req, file, cb) {
cb(null, './public/uploads')
},
filename: function (req, file, cb) {
const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1E9)
cb(null, file.fieldname + '-' + uniqueSuffix + file.originalname)
}
})
app.post('/post/comments', upload.single('image'), async (req, res) => {
const { title, content } = req.body;
const image = req.file;
if (!title || !content) {
return res.status(400).json({ error: 'Title and content are required' });
}
try {
// Upload the image to Cloudinary
const result = await cloudinary.uploader.upload(req.file.path, {
upload_preset: 'ml_default', // Make sure you have a preset in Cloudinary
});
// Log the image URL and other information
console.log('Cloudinary Image URL:', result.secure_url);
console.log('Image Uploaded:', result.original_filename);
// Here, you can save the blog data (title, content, image URL) to your database
// For now, we'll respond with the data
res.status(200).json({
message: 'Blog post received!',
data: { title, content, imageUrl: result.secure_url },
});
} catch (err) {
console.error('Cloudinary upload error:', err);
res.status(500).json({ error: 'Error uploading image to Cloudinary' });
}
});
app.listen(port, () => {
console.log(`Server running on http://localhost:${port}`);
});
📡 Explanation
- Cloudinary Configuration:
We set up Cloudinary using the cloudinary.config() method and input your Cloudinary credentials (cloud_name, api_key, api_secret
). Make sure to replace these values with your actual Cloudinary account credentials.
- Multer Upload:
const storage = multer.diskStorage({
destination: function (req, file, cb) {
cb(null, '/tmp/my-uploads')
},
filename: function (req, file, cb) {
const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1E9)
cb(null, file.fieldname + '-' + uniqueSuffix)
}
})
const upload = multer({ storage: storage })
When using Multer to handle file uploads, there are two important options you can use to control where and how your files are stored: destination and filename
Destination:
This option tells Multer where to save the uploaded files. You can choose a specific folder or directory.
If you give destination a string (like
'./public/uploads'
), Multer will automatically create that folder if it doesn’t already exist.Quick advise : create the folder yourself
If you want more control, you can pass destination as a function. This way, you can decide dynamically which folder to store the file in based on things like the file type or other factors.
Filename:
This option lets you decide what the file should be named once it’s uploaded.
If you don’t specify a filename, Multer will create a random name for the file (but it won’t add the file extension for you).
You can also pass filename as a function, where you can decide exactly how to name the file, including giving it the proper file extension (like
.jpg
,.png
, etc.).
When you see this in the code:
upload.single('image')
Here’s what’s happening:
✅ upload.single()
is a Multer function that tells your server to expect a single file upload.
✅ The string 'image'
is the name of the file field coming from your frontend form, this must match the name
attribute on your <input>
in the frontend form, like:
<input type="file" name="image" />
So basically, this line tells Multer:
👉 “Hey, look for a file in the request called ‘image’ and handle it as a single file upload.”
If you use a different name on the frontend (for example, <input name="avatar" />
), you must update it in the backend too:
upload.single('avatar')
Get it ?
Nice!!!
- Cloudinary Upload:
The image is uploaded using Cloudinary's
upload
function. We're using the upload preset (ml_default
), which should be configured in your Cloudinary dashboard under settings.After a successful upload, we get the image URL (via
result.secure
_url
), which is returned in the response to the frontend.
- Response:
- The backend now responds with a JSON object containing the blog post data and the image URL from Cloudinary, which you can save in a database
📝 Next Steps:
Frontend: You can now display the uploaded image in your frontend app using the Cloudinary URL (
result.secure
_url
).Database: You might want to save the blog data, including the title, content, and Cloudinary image URL, to a database like MongoDB or MySQL.
And if you’re just starting your journey as a backend developer and looking to create your first ever live server?
dont worry, i gotchu
i just created a new beginner friendly blog for people who want to newly transition into Backend development
Do well to check it out Starting your Backend journey
✨ Wrap-up
Alright, let’s bring it all together!
Handling FormData and file uploads might sound tricky at first, but once you break it down, it’s really just about understanding a few key tools. On the frontend, you use FormData to send both text and files (like images) to the backend. On the backend, tools like Express, Multer, and Cloudinary help you receive, process, and store those files safely.
Now, let’s talk about Multer for a moment.
Think of Multer as your backend’s helper or “middleman” when files arrive. When someone submits a form with a file (like an image), Multer steps in and takes care of unpacking that file so you can easily access it in your server code. Without Multer, the server would receive a messy, unreadable stream of data. Multer organizes that data, saves the file somewhere you choose, and hands you clean info about it (like the name, type, and size), all ready to use!
Once Multer has handled the file, you can pass it off to Cloudinary or any other service to store it safely in the cloud.
A few things to always keep in mind:
✅ Validate your files (check their type and size).
✅ Handle errors gracefully so your users get helpful feedback.
✅ Be mindful of security, don’t blindly trust whatever comes from the client!
Once you get the hang of it, you’ll find this pattern useful in so many projects, whether it’s building blogs, profile uploads, or even full media galleries.
Thanks for following along! I hope this guide made things clearer and less intimidating. Keep experimenting, keep building
you’ve got this! 🚀✨
They call me Kingsley, the Lonely/Weird Frontend web developer🚀.
With a solid background in web development (3+ years of experience) and a passion for creating exceptional user experiences, I am excited about the opportunity to contribute my skills to your team.
Throughout my career, I have honed my expertise in ensuring that I can craft responsive and visually appealing websites. I am well-versed in modern frontend frameworks and libraries, allowing me to stay at the forefront of industry trends and best practices.
Collaboration is a cornerstone of my work ethic, and I thrive in team environments. I enjoy turning design concepts into functional, user-friendly interfaces and have a keen eye for detail when it comes to optimizing for performance and accessibility.
I am committed to delivering high-quality code and meeting project deadlines. My adaptability, problem-solving abilities, and dedication to continuous learning make me a valuable asset in fast-paced development environments.
I would welcome the opportunity to discuss how my skills and experience align with your company's goals and how I can contribute to the success of your projects.
Subscribe to my newsletter
Read articles from Okeze Kingsley directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
Okeze Kingsley
Okeze Kingsley
I am a Frontend Developer position. With a solid background in web development (3+ years of experience) and a passion for creating exceptional user experiences, I am excited about the opportunity to contribute my skills to your team. Throughout my career, I have honed my expertise in ensuring that I can craft responsive and visually appealing websites. I am well-versed in modern frontend frameworks and libraries, allowing me to stay at the forefront of industry trends and best practices. Collaboration is a cornerstone of my work ethic, and I thrive in team environments. I enjoy turning design concepts into functional, user-friendly interfaces and have a keen eye for detail when it comes to optimizing for performance and accessibility.