Uploading and storing images is a common requirement in many applications like user profiles, product listings, etc. Instead of storing the image in your database (which is not optimal), we use AWS S3 for storage and save the S3 URL in MongoDB.
✅ What We’ll Do:
- Upload image from React frontend
- Send it to Node.js backend
- Upload image to AWS S3
- Store the image URL in MongoDB
🧰 Prerequisites
- AWS S3 Bucket
- AWS Access Key and Secret Key
- Node.js & Express backend
- MongoDB (Atlas or Local)
- React frontend
🎯 1. Create an S3 Bucket
- Go to AWS S3 Console
- Click Create bucket
- Uncheck Block all public access (for testing)
- Note down the Bucket name, Region
🗝️ 2. Get Your AWS Credentials
Go to IAM → Users → Your User → Security Credentials
Generate an Access Key and Secret Key
🛠️ 3. Backend Setup (Node.js + Express)
📁 Project Structure
backend/
│
├── server.js
├── routes/
│ └── upload.js
├── controllers/
│ └── uploadController.js
├── config/
│ └── s3.js
├── models/
│ └── Image.js
📦 Install Dependencies
npm install express multer aws-sdk mongoose dotenv cors
📄 .env
PORT=5000
MONGO_URI=your_mongo_db_connection_string
AWS_ACCESS_KEY_ID=your_access_key
AWS_SECRET_ACCESS_KEY=your_secret_key
AWS_REGION=your-region
AWS_BUCKET_NAME=your-bucket-name
⚙️ config/s3.js
const AWS = require('aws-sdk');
const s3 = new AWS.S3({
accessKeyId: process.env.AWS_ACCESS_KEY_ID,
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
region: process.env.AWS_REGION,
});
module.exports = s3;
🧠 models/Image.js
const mongoose = require('mongoose');
const imageSchema = new mongoose.Schema({
url: String,
key: String, // optional: used for deleting image from S3
});
module.exports = mongoose.model('Image', imageSchema);
📤 controllers/uploadController.js
const s3 = require('../config/s3');
const Image = require('../models/Image');
const uploadImage = async (req, res) => {
const file = req.file;
if (!file) return res.status(400).json({ message: 'No file uploaded' });
const params = {
Bucket: process.env.AWS_BUCKET_NAME,
Key: `${Date.now()}_${file.originalname}`,
Body: file.buffer,
ACL: 'public-read',
ContentType: file.mimetype,
};
try {
const data = await s3.upload(params).promise();
const image = new Image({ url: data.Location, key: data.Key });
await image.save();
res.status(200).json({ message: 'Image uploaded', image });
} catch (error) {
res.status(500).json({ error: error.message });
}
};
module.exports = { uploadImage };
🛣️ routes/upload.js
const express = require('express');
const router = express.Router();
const multer = require('multer');
const { uploadImage } = require('../controllers/uploadController');
const storage = multer.memoryStorage();
const upload = multer({ storage });
router.post('/upload', upload.single('image'), uploadImage);
module.exports = router;
🚀 server.js
const express = require('express');
const mongoose = require('mongoose');
const cors = require('cors');
require('dotenv').config();
const app = express();
app.use(cors());
app.use(express.json());
app.use('/api', require('./routes/upload'));
mongoose.connect(process.env.MONGO_URI)
.then(() => {
console.log('MongoDB connected');
app.listen(process.env.PORT, () => console.log(`Server on ${process.env.PORT}`));
})
.catch(err => console.log(err));
🧑🎨 4. Frontend Setup (React)
📦 Install Axios
npm install axios
📂 ImageUpload.jsx
import React, { useState } from 'react';
import axios from 'axios';
const ImageUpload = () => {
const [file, setFile] = useState(null);
const [preview, setPreview] = useState('');
const [uploadedImage, setUploadedImage] = useState(null);
const handleFileChange = (e) => {
const selected = e.target.files[0];
setFile(selected);
setPreview(URL.createObjectURL(selected));
};
const handleUpload = async () => {
if (!file) return alert('Please select an image');
const formData = new FormData();
formData.append('image', file);
try {
const res = await axios.post('http://localhost:5000/api/upload', formData, {
headers: { 'Content-Type': 'multipart/form-data' },
});
setUploadedImage(res.data.image);
} catch (err) {
console.error(err);
}
};
return (
<div>
<h2>Upload Image</h2>
<input type="file" accept="image/*" onChange={handleFileChange} />
{preview && <img src={preview} alt="preview" style={{ width: '200px', marginTop: '10px' }} />}
<br />
<button onClick={handleUpload}>Upload</button>
{uploadedImage && (
<div style={{ marginTop: '20px' }}>
<p>Uploaded Image URL:</p>
<a href={uploadedImage.url} target="_blank" rel="noopener noreferrer">{uploadedImage.url}</a>
<img src={uploadedImage.url} alt="Uploaded" style={{ width: '200px', display: 'block', marginTop: '10px' }} />
</div>
)}
</div>
);
};
export default ImageUpload;
✅ Output
When a user uploads an image:
- React sends it to Node.js
- Node.js uploads it to AWS S3
- The URL is saved to MongoDB
- The image is previewed on the frontend
🧽 Bonus: Cleaning Up
To delete from S3:
s3.deleteObject({
Bucket: process.env.AWS_BUCKET_NAME,
Key: imageKey,
}).promise();
🧠 Conclusion
This setup is scalable, secure, and cost-efficient. Using AWS S3 for image storage is industry-standard, and MongoDB just stores lightweight URLs.
If you want, I can generate a GitHub repo, Postman collection, or even TypeScript version of this. Just let me know!
Thanks for sharing, it’s helpful to setup AWS S3 in my Full Stack Project.