Absolutely. This is actually a great habit. Before starting the frontend tomorrow, it's worth making sure you understand why you built each backend component, not just how.
Build a production-ready REST API that allows users to:
- Register/Login
- Upload waste images
- Store images on Cloudinary
- Analyze images using Gemini AI
- Save scan history
- View dashboard statistics
- Manage their profile
Client (React)
│
│ HTTP Request
▼
Express Server
│
├── Routes
│
▼
Controllers
│
├── Validation
├── Authentication
├── Business Logic
│
▼
Services
│
├── Cloudinary
└── Gemini AI
│
▼
MongoDB
Acts as the backend server.
It:
- Receives requests
- Sends responses
- Connects everything
Example:
POST /api/auth/login
↓
Express receives it
↓
Sends it to
loginController()
Purpose:
Store data.
Collections:
Users
Scans
User Example
{
"name":"Srujan",
"email":"abc@gmail.com",
"password":"hashed"
}Scan Example
{
"user":"ObjectId",
"imageUrl":"cloudinary url",
"wasteType":"Plastic Bottle",
"ecoScore":85
}Mongoose creates schemas so every document has the correct structure.
Flow
Register
↓
Password hashed using bcrypt
↓
Saved in MongoDB
Login
↓
Check email
↓
Compare password
↓
Generate JWT
↓
Return JWT
Every protected request
↓
JWT
↓
authMiddleware
↓
Verify token
↓
req.user
Now every controller knows who is making the request.
Purpose
Never save passwords.
Instead of
password123
Database stores
$2b$10$afkdhfkj...
During login
bcrypt.compare()
checks the password.
Creates a signed token.
Example
Header
Payload
Signature
The client stores the token.
Every request
Authorization
Bearer TOKEN
Middleware verifies it.
If valid
req.user
becomes available.
Middleware runs before controllers.
Flow
Request
↓
Middleware
↓
Controller
Examples
Checks JWT.
uploadMiddleware
Uses multer.
Stores image inside memory.
validationMiddleware
Uses Joi.
Stops invalid requests.
errorMiddleware
Catches every error.
Instead of crashing
Returns JSON.
rateLimiter
Stops spam.
Example
100 requests
↓
Blocked
Helmet
Adds security headers.
Protects Express.
Compression
Compresses responses.
Smaller JSON.
Faster API.
Morgan
Logs requests.
Example
POST /login
200
38ms
Uploads images.
Instead of saving locally
uploads/
We used
memoryStorage()
Image becomes
req.file.buffer
Then directly sent to Cloudinary.
Purpose
Image storage.
Flow
Image
↓
Buffer
↓
Cloudinary
↓
Image URL
↓
MongoDB
Database never stores images.
Only URL.
Flow
Cloudinary URL
↓
Gemini Vision
↓
Analysis
↓
JSON
↓
Database
Returns
Waste Type
Category
Confidence
Eco Score
Recycling Instructions
Environmental Impact
Controllers should not contain external API logic.
So we created
cloudinaryService
geminiService
Controller simply calls
uploadToCloudinary()
analyzeWaste()
Very clean architecture.
Controllers contain business logic.
Example
Upload Image
↓
Cloudinary
↓
Gemini
↓
Save Scan
↓
Return Response
Controllers never talk directly to UI.
Routes map URLs.
Example
POST
/api/auth/login
↓
loginController
Routes are just traffic managers.
Instead of
email="abc"
accepting anything,
Joi checks
Email
Password
Required Fields
before controller executes.
Cleaner.
Safer.
Instead of returning every scan,
MongoDB aggregates.
Returns
Total Scans
Recyclable
Non-Recyclable
Average Eco Score
Recent Scans
One request.
Very efficient.
Purpose
API Documentation.
Instead of opening Postman,
Developers visit
/api-docs
and can test APIs directly.
Professional touch.
Instead of
try
catch
inside every controller,
we created
asyncHandler
↓
errorMiddleware
Every error goes to one place.
Cleaner code.
src/
│
├── config/
│ mongodb
│ cloudinary
│
├── controllers/
│
├── middleware/
│
├── models/
│
├── routes/
│
├── services/
│
├── utils/
│
├── validations/
│
├── docs/
│
├── app.js
│
└── server.js
Every folder has one responsibility, which is exactly how production backends are organized.
- How authentication really works (JWT + bcrypt)
- Why middleware exists and how it fits into the request lifecycle
- How file uploads work using Multer and in-memory buffers
- How to integrate third-party services like Cloudinary and Gemini
- Why services separate external API logic from controllers
- How to design REST APIs with clear routes and controllers
- How to validate input before business logic
- How to centralize error handling
- How to secure an Express application with Helmet, rate limiting, and compression
- How to document APIs with Swagger
Tomorrow we'll start the frontend from scratch with the same mindset we used for the backend—building it like a production application, not just making it work.
We'll cover:
- React architecture and folder structure
- Tailwind CSS setup
- React Router
- Authentication flow with JWT
- Axios configuration
- Protected routes
- Dashboard UI
- AI Scanner page
- Scan History
- Profile & Settings
- Responsive design and animations
- Deployment to Vercel
By the end, you'll have a complete AI-powered MERN application with a backend you understand and a frontend built using modern React practices.
You've made excellent progress today—especially debugging the Cloudinary issue yourself. That kind of debugging experience is what really builds confidence as a developer. Tomorrow we'll turn all of this backend functionality into a polished user experience. 🚀