Uploading and Displaying Images with Node.js, Multer, Oracle DB & React

Working with file uploads can feel complex—especially when storing images directly in a database. In this blog, we’ll walk through the complete flow:
How to upload images from a React frontend
Process and store them using Multer middleware
Save the image data as a BLOB (binary) in Oracle DB
Retrieve and display them on the frontend as base64-encoded images
🔧 Backend Setup: Node.js + Express + Multer
Step 1: Install Required Packages
bashCopyEditnpm install express multer oracledb sequelize
Step 2: Multer Configuration
Multer handles multipart/form-data
used for uploading files.
jsCopyEdit// middleware/multer.js
const multer = require('multer');
const storage = multer.memoryStorage(); // Store image in memory buffer
const upload = multer({ storage: storage });
module.exports = upload;
✅
memoryStorage()
keeps the file in memory as a Buffer✅ We will later save this Buffer directly into Oracle DB
Step 3: Define Sequelize Model
jsCopyEdit// models/Image.js
module.exports = (sequelize, DataTypes) => {
return sequelize.define('Image', {
id: { type: DataTypes.INTEGER, autoIncrement: true, primaryKey: true },
description: DataTypes.STRING,
type: DataTypes.STRING,
image: DataTypes.BLOB('long'), // Store binary data
}, {
tableName: 'images',
timestamps: false
});
};
Step 4: Upload Controller
jsCopyEdit// controllers/imageController.js
exports.createImage = async (req, res) => {
try {
const { description, type } = req.body;
const imageBuffer = req.file ? req.file.buffer : null;
const newImage = await Image.create({ description, type, image: imageBuffer });
res.status(201).json({ message: 'Image created', id: newImage.id });
} catch (err) {
console.error(err);
res.status(500).json({ error: 'Failed to create image' });
}
};
Step 5: Retrieve Controller (Base64 Conversion)
jsCopyEditexports.getImages = async (req, res) => {
try {
const images = await Image.findAll();
const result = images.map(img => ({
id: img.id,
description: img.description,
type: img.type,
image: img.image ? img.image.toString('base64') : null
}));
res.status(200).json(result);
} catch (err) {
console.error(err);
res.status(500).json({ error: 'Failed to fetch images' });
}
};
🎯 Frontend: Uploading Images with React + React Hook Form + Tailwind CSS
Upload Component
jsxCopyEdit// ImageUpload.js
import { useForm } from 'react-hook-form';
import axios from 'axios';
const ImageUpload = () => {
const { register, handleSubmit, reset } = useForm();
const onSubmit = async (data) => {
const formData = new FormData();
formData.append('description', data.description);
formData.append('type', data.type);
formData.append('image', data.image[0]);
try {
await axios.post('http://localhost:4000/api/v1/image/createimage', formData, {
headers: { 'Content-Type': 'multipart/form-data' }
});
alert("Image uploaded!");
reset();
} catch (error) {
console.error(error);
}
};
return (
<form onSubmit={handleSubmit(onSubmit)} className="p-6 space-y-4 bg-white shadow rounded-xl max-w-md mx-auto">
<input type="text" {...register("description")} placeholder="Description" className="border p-2 w-full rounded" />
<input type="text" {...register("type")} placeholder="Type" className="border p-2 w-full rounded" />
<input type="file" {...register("image")} className="border p-2 w-full rounded" />
<button type="submit" className="bg-blue-600 text-white py-2 px-4 rounded">Upload</button>
</form>
);
};
export default ImageUpload;
🖼️ Image Gallery Component (Display)
jsxCopyEdit// ImageGallery.js
import { useEffect, useState } from 'react';
import axios from 'axios';
const ImageGallery = () => {
const [images, setImages] = useState([]);
useEffect(() => {
axios.get('http://localhost:4000/api/v1/image/getimages')
.then(res => setImages(res.data))
.catch(err => console.error(err));
}, []);
return (
<div className="grid grid-cols-2 sm:grid-cols-3 gap-4 p-4">
{images.map(img => (
<div key={img.id} className="border p-4 rounded shadow">
<img src={`data:image/jpeg;base64,${img.image}`} alt="Uploaded" className="w-full h-48 object-cover rounded" />
<p className="font-semibold">{img.description}</p>
<p className="text-sm text-gray-600">{img.type}</p>
</div>
))}
</div>
);
};
export default ImageGallery;
🔍 Deep Dive: What's Happening Behind the Scenes?
📦 Multer Buffer
Multer parses the uploaded image and stores it in
req.file.buffer
This
Buffer
is just a raw binary representation of the imageWe insert that buffer directly into the Oracle DB as a
BLOB
🧠 Oracle DB
The image is saved in binary format (not base64 or path)
Oracle's BLOB column is designed to store large binary data like images or videos
🔄 From DB to Frontend
On fetching, we convert binary back to base64
We prefix the base64 string with:
bashCopyEditdata:image/jpeg;base64,
This turns the base64 into a Data URI, allowing us to display the image directly in an
<img>
tag
🧼 Summary
Step | What Happens |
1️⃣ Upload | React form sends image using FormData |
2️⃣ Multer | Middleware parses the image to Buffer |
3️⃣ DB Save | Binary buffer stored in Oracle DB as BLOB |
4️⃣ Fetch | Image retrieved from DB and converted to base64 |
5️⃣ Display | Base64 + Data URI used in React <img> |
📌 Final Thoughts
💡 Using base64 is great for small/medium images; for large files, consider CDN + path storage
🔐 Always validate and sanitize files server-side
🚀 This approach works with any frontend framework (Vue, Angular, etc.)
Subscribe to my newsletter
Read articles from Sagar Jadhav directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
