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

Sagar JadhavSagar Jadhav
4 min read

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;

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 image

  • We 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

StepWhat Happens
1️⃣ UploadReact form sends image using FormData
2️⃣ MulterMiddleware parses the image to Buffer
3️⃣ DB SaveBinary buffer stored in Oracle DB as BLOB
4️⃣ FetchImage retrieved from DB and converted to base64
5️⃣ DisplayBase64 + 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.)

0
Subscribe to my newsletter

Read articles from Sagar Jadhav directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Sagar Jadhav
Sagar Jadhav