DEV Community

SMITHA YENUGU
SMITHA YENUGU

Posted on

ConnectNow: A Full-Stack Social Media App from Scratch

A journey from React basics to production deployment — lessons learned building a real-world social networking platform


The Problem

I wanted to learn full-stack development, but most tutorials felt disconnected from reality. Todo apps and weather widgets don't teach you about real-world challenges like:

  • Managing complex database relationships (users, posts, comments, messages)
  • Handling authentication securely at scale
  • Dealing with Render's ephemeral filesystem destroying uploads
  • Building responsive UIs that actually work on mobile

So I decided to build something ambitious: ConnectNow — a full-stack social media platform with posts, messaging, profiles, and real-time interactions.


The Architecture

ConnectNow has three independent pieces deployed separately:

Frontend (React + Vercel)
        ↓ HTTP
Backend (Node.js/Express + Render)
        ↓
Database (MongoDB Atlas)
Enter fullscreen mode Exit fullscreen mode

Why separate frontend and backend?

  • Decoupling – My backend can serve multiple clients (web, mobile, third-party integrations)
  • Parallel development – Frontend and backend teams could work independently
  • Independent scaling – If one part gets hammered with traffic, I can scale it without scaling the other

The Frontend Stack

  • React.js with hooks for state management
  • CSS3 for responsive design (mobile-first)
  • React Router for navigation
  • Deployed on Vercel for automatic deployments on every git push

The frontend is straightforward: it's just a single-page application that talks to the backend API. The complexity is in the interactions — real-time message updates, smooth theme switching, proper authorization checks.

The Backend Stack

  • Node.js + Express.js for the REST API
  • MongoDB for the database
  • JWT for stateless authentication
  • BCrypt for password hashing
  • Google OAuth for social login
  • Cloudinary for cloud image storage (this became crucial!)
  • Deployed on Render for $0/month (free tier)

The Database Design

This was the most challenging part. A social media app has complex relationships:

A User can:
  - Create many Posts
  - Like many Posts
  - Follow many other Users
  - Send many Messages
  - Have many Connections (followers)

A Post belongs to one User
  - Can have many Likes
  - Can have many Comments
  - Can have one Image

A Message belongs to two Users (sender & receiver)
  - Can be edited
  - Can be deleted
Enter fullscreen mode Exit fullscreen mode

I normalized the schema to avoid data duplication:

// Users collection
{
  _id: ObjectId,
  email: "user@example.com",
  password: "hashed_with_bcrypt",
  profile_picture: "cloudinary_url",
  followers: [userId, userId, ...],  // Array of follower IDs
  following: [userId, userId, ...],
  created_at: Date
}

// Posts collection
{
  _id: ObjectId,
  author: userId,  // Reference, not embedded
  title: "Amazing View",
  description: "...",
  image: "cloudinary_url",
  likes: [userId, userId, ...],
  comments: [commentId, commentId, ...],
  created_at: Date
}

// Comments collection
{
  _id: ObjectId,
  post_id: postId,
  author: userId,
  text: "Very nice post!",
  created_at: Date
}

// Messages collection
{
  _id: ObjectId,
  sender: userId,
  recipient: userId,
  text: "Hey! How are you?",
  is_edited: false,
  created_at: Date,
  deleted_at: null
}
Enter fullscreen mode Exit fullscreen mode

Key decision: I used arrays of IDs instead of embedding full documents. Why?

  • Memory efficient — I'm not duplicating user data in every message
  • Flexible queries — I can efficiently find "all posts liked by user X"
  • Scalability — If I need to change a user's name, I update it once, everywhere

Challenges & Solutions

🚨 Challenge #1: Images Disappearing on Render

The Problem:
After deploying to Render's free tier, I noticed a nightmare: all uploaded images disappeared after an hour.

Render's free tier uses an ephemeral filesystem — any files you write are deleted when the dyno restarts. This is by design to save costs, but it broke my file upload system.

The Solution:
I switched to Cloudinary, a cloud image hosting service. Now:

  1. User uploads image → sent to Cloudinary
  2. Cloudinary returns a permanent URL
  3. That URL is stored in MongoDB
  4. Even if Render restarts, the image link persists

This was a learning moment: don't store files on servers that might restart. Use cloud storage (S3, GCS, Cloudinary, etc.).

// Before (broken):
const filename = `${Date.now()}_${req.file.filename}`;
fs.writeFileSync(`uploads/${filename}`, req.file.buffer);  // ❌ Lost on restart

// After (works):
const result = await cloudinary.uploader.upload_stream(...);
const imageUrl = result.secure_url;  // ✅ Permanent URL
Enter fullscreen mode Exit fullscreen mode

🚨 Challenge #2: "Can't find module 'mongoose'"

The Problem:
Local development worked fine, but production (Render) crashed on startup: "Cannot find module 'mongoose'".

The Root Cause:
I forgot to commit node_modules/ (correctly — it's in .gitignore). But I also didn't commit package-lock.json — so when Render ran npm install, it pulled slightly different versions that were incompatible.

The Solution:
Always commit package-lock.json. This ensures everyone (including your deployment platform) uses the exact same dependencies.

git add package-lock.json
git commit -m "Add package-lock for reproducible builds"
Enter fullscreen mode Exit fullscreen mode

🚨 Challenge #3: CORS Errors When Frontend Calls Backend

The Problem:

Access to XMLHttpRequest at 'https://connectnow-backend.onrender.com/...' 
from origin 'https://connect-now-bice.vercel.app' has been blocked by CORS policy
Enter fullscreen mode Exit fullscreen mode

My frontend couldn't talk to my backend because of Cross-Origin Resource Sharing (CORS) restrictions.

The Solution:
Configure CORS on the backend to allow requests from the frontend domain:

const cors = require('cors');

app.use(cors({
  origin: 'https://connect-now-bice.vercel.app',  // Only allow this domain
  credentials: true  // Allow cookies for auth
}));
Enter fullscreen mode Exit fullscreen mode

Security lesson: Never use cors({ origin: '*' }) in production — that's like leaving your front door open. Whitelist only the domains you trust.


Technical Wins

✅ Real-Time Messaging

Users can send messages, and the UI updates instantly. I achieved this with a simple polling strategy:

  • Frontend fetches messages every 500ms
  • Backend returns only new messages since last fetch
  • This is lighter than WebSockets for a small app

For a production app with millions of users, I'd use WebSockets or Firebase Realtime Database, but polling works great for learning.

✅ Secure Authentication

Users log in with email/password or Google OAuth. Here's how I kept it secure:

// 1. Hash passwords before storing
const hashedPassword = await bcrypt.hash(password, 10);
await User.create({ email, password: hashedPassword });

// 2. Verify password on login
const isValid = await bcrypt.compare(inputPassword, storedHash);

// 3. Issue JWT token
const token = jwt.sign({ userId }, 'SECRET_KEY', { expiresIn: '7d' });

// 4. Require token for protected routes
app.get('/api/profile', authenticateToken, (req, res) => {
  // Only authenticated users reach here
});
Enter fullscreen mode Exit fullscreen mode

✅ Password Reset Flow

I implemented a proper email-based password reset:

  1. User clicks "Forgot Password"
  2. Backend generates a unique reset token (valid for 15 minutes)
  3. Email is sent with reset link
  4. User clicks link → sets new password
  5. Token is invalidated

This is much better than "security questions" or "call customer support".


What I Learned

1. Database Design is 80% of the Work

Most complexity in backends comes from the data model. Getting schema relationships wrong early means painful refactoring later.

2. Deployment is Not the End, It's the Beginning

The hardest bugs happen in production, not local development. I had to debug:

  • Why images disappeared (Render's filesystem)
  • Why auth tokens weren't persisting (CORS cookies)
  • Why messages weren't syncing (MongoDB connection pooling)

3. Security Requires Constant Vigilance

One small mistake (like hardcoding API keys in frontend code) can compromise everything. I learned to:

  • Use environment variables for secrets
  • Validate input on the backend (never trust the client)
  • Hash passwords, don't store plaintext
  • Use HTTPS everywhere

4. Your Database is Your Bottleneck

As I added features, every page load was making 10+ database queries. I learned to:

  • Use database indexes on frequently queried fields
  • Combine queries where possible
  • Cache results that don't change often

Deployment Checklist

By the end, here's what a proper deployment looked like:

  • ✅ Frontend built and minified
  • ✅ Environment variables set on deployment platform
  • ✅ Database migrations run
  • ✅ CORS configured for production domain
  • ✅ SSL/HTTPS enforced
  • ✅ Error logging set up (Sentry, LogRocket, etc.)
  • ✅ API rate limiting enabled

The Result

ConnectNow is now live at https://connect-now-bice.vercel.app.

You can:

  • Create an account (or test with test@example.com)
  • Create posts with images
  • Like, comment, and share
  • Send messages to friends
  • Search for new users
  • Toggle dark/light mode

The app handles real-time interactions, secure authentication, image uploads, and responsive design — all the core skills needed for production full-stack development.


What's Next?

If I were to continue this project, I'd add:

  • WebSockets for true real-time messaging
  • Push notifications for new messages
  • Video calling (WebRTC)
  • Post analytics (who viewed your posts)
  • Content moderation (flagging inappropriate posts)
  • Performance monitoring (understand bottlenecks)

But for now, ConnectNow demonstrates the fundamentals: how to design, build, deploy, and maintain a real-world full-stack application.


Key Takeaways for Aspiring Full-Stack Developers

  1. Start with a real problem, not a tutorial. Building something you care about keeps you motivated through the hard parts.

  2. Get to deployment early. Bugs that only appear in production teach you things localhost never will.

  3. Security isn't optional. Treat it as a core feature from day one, not an afterthought.

  4. Database design matters more than framework choice. Spend time getting the schema right.

  5. Ship imperfect code. You learn more from a deployed app with 100 bugs than a perfect local app with zero users.


Have you built a full-stack app? What was your biggest challenge? Drop a comment below — I'd love to hear about it.

Happy building! 🚀


ConnectNow source code: https://github.com/smithayenugu/connectNow

Top comments (0)