├── frontend ├── src │ ├── App.css │ ├── api │ │ ├── auth.js │ │ ├── posts.js │ │ ├── users.js │ │ ├── axios.js │ │ ├── api.js │ │ └── comments.js │ ├── redux │ │ ├── user.Slice.js │ │ ├── store.js │ │ ├── postSlice.js │ │ └── authSlice.js │ ├── index.css │ ├── components │ │ ├── ImageUpload.jsx │ │ ├── FollowButton.jsx │ │ ├── Layout.jsx │ │ ├── ProfileHeader.jsx │ │ ├── Sidebar.jsx │ │ ├── Navbar.jsx │ │ ├── PostCard.jsx │ │ └── CommentBox.jsx │ ├── main.jsx │ ├── App.jsx │ ├── pages │ │ ├── Home.jsx │ │ ├── CreatePost.jsx │ │ ├── Comment.jsx │ │ ├── Login.jsx │ │ ├── Profile.jsx │ │ └── Register.jsx │ └── assets │ │ └── react.svg ├── public │ ├── favicon.ico │ ├── index.html │ └── vite.svg ├── .gitignore ├── postcss.config.js ├── vite.config.js ├── index.html ├── tailwind.config.js ├── README.md ├── eslint.config.js └── package.json ├── backend ├── models │ ├── comment.js │ ├── user.js │ └── post.js ├── .gitignore ├── routes │ ├── health.js │ ├── auth.js │ ├── post.js │ └── user.js ├── README.md ├── middleware │ └── authMiddleware.js ├── package.json ├── public │ ├── js │ │ └── main.js │ └── css │ │ └── style.css ├── index.js ├── controllers │ ├── authController.js │ ├── postController.js │ └── userController.js ├── views │ └── index.pug └── package-lock.json └── README.md /frontend/src/App.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /backend/models/comment.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/public/favicon.ico: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/src/api/auth.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/src/api/posts.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/src/api/users.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/src/redux/user.Slice.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /backend/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .env -------------------------------------------------------------------------------- /frontend/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .env -------------------------------------------------------------------------------- /frontend/src/index.css: -------------------------------------------------------------------------------- 1 | 2 | @tailwind base; 3 | @tailwind components; 4 | @tailwind utilities; 5 | 6 | -------------------------------------------------------------------------------- /frontend/postcss.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | }; 7 | -------------------------------------------------------------------------------- /frontend/public/index.html: -------------------------------------------------------------------------------- 1 | 2 |
3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /frontend/src/api/axios.js: -------------------------------------------------------------------------------- 1 | import axios from "axios"; 2 | 3 | const API = axios.create({ 4 | baseURL: "http://localhost:4000/api", // ✅ points to backend 5 | }); 6 | 7 | export default API; 8 | -------------------------------------------------------------------------------- /frontend/vite.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import react from '@vitejs/plugin-react' 3 | 4 | // https://vite.dev/config/ 5 | export default defineConfig({ 6 | plugins: [react()], 7 | }) 8 | -------------------------------------------------------------------------------- /backend/routes/health.js: -------------------------------------------------------------------------------- 1 | import express from "express"; 2 | 3 | 4 | export const healthRouter = express.Router(); 5 | 6 | 7 | healthRouter.get('/', (req, res) => { 8 | res.status(200).json({ 9 | "status": "ok" 10 | }) 11 | }); -------------------------------------------------------------------------------- /backend/README.md: -------------------------------------------------------------------------------- 1 | # Backend API Template 2 | 3 | 4 | ## Instructions 5 | 6 | 1. Clone the repo `git clone ` 7 | 2. cd into your new project folder and run `npm i` 8 | 3. Create a new `.env` file and add the `MONGODB_URI` 9 | 4. Run the app with: `npm run dev` -------------------------------------------------------------------------------- /frontend/src/api/api.js: -------------------------------------------------------------------------------- 1 | import axios from "axios"; 2 | 3 | const API = axios.create({ 4 | baseURL: "http://localhost:4000/api", // make sure this matches your Express server URL 5 | withCredentials: true, // optional: only needed if you're dealing with cookies 6 | }); 7 | 8 | export default API; -------------------------------------------------------------------------------- /frontend/src/redux/store.js: -------------------------------------------------------------------------------- 1 | import { configureStore } from "@reduxjs/toolkit"; 2 | import authReducer from "./authSlice"; 3 | import postReducer from "./postSlice"; 4 | 5 | const store = configureStore({ 6 | reducer: { 7 | auth: authReducer, 8 | posts: postReducer, 9 | }, 10 | }); 11 | 12 | export default store; 13 | -------------------------------------------------------------------------------- /frontend/src/api/comments.js: -------------------------------------------------------------------------------- 1 | import API from "./axios"; 2 | 3 | const commentsAPI = { 4 | addComment: (postId, data) => API.post(`/comment/${postId}`, data), 5 | deleteComment: (commentId) => API.delete(`/comment/${commentId}`), 6 | updateComment: (commentId, data) => API.put(`/comment/${commentId}`, data), 7 | }; 8 | 9 | export default commentsAPI; 10 | -------------------------------------------------------------------------------- /frontend/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Creatify 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /frontend/src/redux/postSlice.js: -------------------------------------------------------------------------------- 1 | import { createSlice } from '@reduxjs/toolkit'; 2 | 3 | const postSlice = createSlice({ 4 | name: 'posts', 5 | initialState: [], 6 | reducers: { 7 | addPost: (state, action) => { 8 | state.push(action.payload); 9 | }, 10 | // add more reducers as needed 11 | }, 12 | }); 13 | 14 | // ✅ Export the actions 15 | export const { addPost } = postSlice.actions; 16 | 17 | // ✅ Export reducer as default 18 | export default postSlice.reducer; -------------------------------------------------------------------------------- /frontend/src/components/ImageUpload.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | const ImageUpload = ({ onImageChange }) => { 4 | return ( 5 |
6 | 7 | 13 |
14 | ); 15 | }; 16 | 17 | export default ImageUpload; -------------------------------------------------------------------------------- /frontend/tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | export default { 3 | content: [ 4 | "./index.html", 5 | "./src/**/*.{js,jsx,ts,tsx}", 6 | ], 7 | theme: { 8 | extend: { 9 | colors: { 10 | primary: "#7e22ce", 11 | secondary: "#f3f4f6", 12 | accent: "#e879f9", 13 | }, 14 | fontFamily: { 15 | sans: ["Inter", "ui-sans-serif", "system-ui"], 16 | }, 17 | }, 18 | }, 19 | plugins: [], 20 | }; 21 | -------------------------------------------------------------------------------- /backend/middleware/authMiddleware.js: -------------------------------------------------------------------------------- 1 | import jwt from "jsonwebtoken" 2 | 3 | const authMiddleware = (req, res, next) => { 4 | const token = req.header("Authorization"); 5 | 6 | if (!token) return res.status(401).json({ message: "Access denied" }); 7 | 8 | try { 9 | const verified = jwt.verify(token, process.env.JWT_SECRET); 10 | req.user = verified; 11 | next(); 12 | } catch (err) { 13 | res.status(401).json({ message: "Invalid token" }); 14 | } 15 | }; 16 | 17 | export default authMiddleware; 18 | -------------------------------------------------------------------------------- /frontend/src/main.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom/client'; 3 | import App from './App'; 4 | import './index.css'; 5 | import "@fontsource/inter/400.css"; 6 | import "@fontsource/inter/600.css"; 7 | import "@fontsource/inter/700.css"; 8 | 9 | 10 | import { Provider } from 'react-redux'; 11 | import store from './redux/store'; 12 | 13 | ReactDOM.createRoot(document.getElementById('root')).render( 14 | 15 | 16 | 17 | 18 | 19 | ); 20 | -------------------------------------------------------------------------------- /frontend/src/components/FollowButton.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | 3 | const FollowButton = () => { 4 | const [isFollowing, setIsFollowing] = useState(false); 5 | return ( 6 | 14 | ); 15 | }; 16 | 17 | export default FollowButton; -------------------------------------------------------------------------------- /backend/models/user.js: -------------------------------------------------------------------------------- 1 | import mongoose from "mongoose"; 2 | 3 | const userSchema = new mongoose.Schema({ 4 | username: { type: String, required: true, unique: true }, 5 | email: { type: String, required: true, unique: true }, 6 | password: { type: String, required: true }, 7 | profilePic: { type: String, default: "" }, 8 | followers: [{ type: mongoose.Schema.Types.ObjectId, ref: "User" }], 9 | following: [{ type: mongoose.Schema.Types.ObjectId, ref: "User" }], 10 | }, { timestamps: true }); 11 | 12 | const User = mongoose.models.User || mongoose.model("User", userSchema); 13 | export default User; // ✅ safe and re-usable 14 | -------------------------------------------------------------------------------- /backend/models/post.js: -------------------------------------------------------------------------------- 1 | import mongoose from "mongoose" 2 | 3 | const postSchema = new mongoose.Schema({ 4 | user: { type: mongoose.Schema.Types.ObjectId, ref: "User", required: true }, 5 | imageUrl: { type: String, required: true }, 6 | caption: { type: String }, 7 | likes: [{ type: mongoose.Schema.Types.ObjectId, ref: "User" }], 8 | comments: [ 9 | { 10 | user: { type: mongoose.Schema.Types.ObjectId, ref: "User" }, 11 | text: { type: String }, 12 | createdAt: { type: Date, default: Date.now } 13 | } 14 | ] 15 | }, { timestamps: true }); 16 | 17 | export default mongoose.model("Post", postSchema); 18 | -------------------------------------------------------------------------------- /frontend/src/components/Layout.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import Navbar from "./Navbar"; 3 | import Sidebar from "./Sidebar"; 4 | 5 | const Layout = ({ children }) => { 6 | return ( 7 |
8 | 9 |
10 | {/* Sidebar */} 11 | 14 | 15 | {/* Main Content */} 16 |
17 | {children} 18 |
19 |
20 |
21 | ); 22 | }; 23 | 24 | export default Layout; 25 | -------------------------------------------------------------------------------- /backend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "backend-template", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "type": "module", 7 | "scripts": { 8 | "dev": "nodemon index.js", 9 | "start": "node index.js" 10 | }, 11 | "keywords": [], 12 | "author": "Abe Tavarez", 13 | "license": "ISC", 14 | "dependencies": { 15 | "bcryptjs": "^3.0.2", 16 | "cors": "^2.8.5", 17 | "dotenv": "^16.4.7", 18 | "express": "^4.21.2", 19 | "helmet": "^8.0.0", 20 | "jsonwebtoken": "^9.0.2", 21 | "mongoose": "^8.12.1", 22 | "multer": "^1.4.5-lts.2", 23 | "pug": "^3.0.3" 24 | }, 25 | "devDependencies": { 26 | "morgan": "^1.10.0", 27 | "nodemon": "^3.1.9" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /frontend/src/components/ProfileHeader.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | const ProfileHeader = ({ user }) => { 4 | return ( 5 |
6 | avatar 11 |
12 |

{user.username}

13 |

{user.bio}

14 |
15 | {user.followers} Followers 16 | {user.following} Following 17 |
18 |
19 |
20 | ); 21 | }; 22 | 23 | export default ProfileHeader; 24 | -------------------------------------------------------------------------------- /frontend/README.md: -------------------------------------------------------------------------------- 1 | # React + Vite 2 | 3 | This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. 4 | 5 | Currently, two official plugins are available: 6 | 7 | - [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh 8 | - [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh 9 | 10 | ## Expanding the ESLint configuration 11 | 12 | If you are developing a production application, we recommend using TypeScript and enable type-aware lint rules. Check out the [TS template](https://github.com/vitejs/vite/tree/main/packages/create-vite/template-react-ts) to integrate TypeScript and [`typescript-eslint`](https://typescript-eslint.io) in your project. 13 | -------------------------------------------------------------------------------- /frontend/src/components/Sidebar.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Link } from "react-router-dom"; 3 | 4 | const Sidebar = () => { 5 | return ( 6 |
7 |

Navigation

8 | 15 |
16 | ); 17 | }; 18 | 19 | export default Sidebar; -------------------------------------------------------------------------------- /frontend/eslint.config.js: -------------------------------------------------------------------------------- 1 | import js from '@eslint/js' 2 | import globals from 'globals' 3 | import reactHooks from 'eslint-plugin-react-hooks' 4 | import reactRefresh from 'eslint-plugin-react-refresh' 5 | 6 | export default [ 7 | { ignores: ['dist'] }, 8 | { 9 | files: ['**/*.{js,jsx}'], 10 | languageOptions: { 11 | ecmaVersion: 2020, 12 | globals: globals.browser, 13 | parserOptions: { 14 | ecmaVersion: 'latest', 15 | ecmaFeatures: { jsx: true }, 16 | sourceType: 'module', 17 | }, 18 | }, 19 | plugins: { 20 | 'react-hooks': reactHooks, 21 | 'react-refresh': reactRefresh, 22 | }, 23 | rules: { 24 | ...js.configs.recommended.rules, 25 | ...reactHooks.configs.recommended.rules, 26 | 'no-unused-vars': ['error', { varsIgnorePattern: '^[A-Z_]' }], 27 | 'react-refresh/only-export-components': [ 28 | 'warn', 29 | { allowConstantExport: true }, 30 | ], 31 | }, 32 | }, 33 | ] 34 | -------------------------------------------------------------------------------- /backend/public/js/main.js: -------------------------------------------------------------------------------- 1 | document.addEventListener("DOMContentLoaded", () => { 2 | // Add copy functionality to code blocks 3 | document.querySelectorAll("pre code").forEach((block) => { 4 | block.addEventListener("click", () => { 5 | const text = block.textContent; 6 | navigator.clipboard.writeText(text).then(() => { 7 | // Show a brief "Copied!" message 8 | const message = document.createElement("div"); 9 | message.textContent = "Copied!"; 10 | message.style.cssText = ` 11 | position: fixed; 12 | top: 20px; 13 | right: 20px; 14 | background: #28a745; 15 | color: white; 16 | padding: 10px 20px; 17 | border-radius: 5px; 18 | transition: opacity 0.3s; 19 | `; 20 | document.body.appendChild(message); 21 | setTimeout(() => { 22 | message.style.opacity = "0"; 23 | setTimeout(() => message.remove(), 300); 24 | }, 2000); 25 | }); 26 | }); 27 | }); 28 | }); -------------------------------------------------------------------------------- /frontend/src/App.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { BrowserRouter as Router, Routes, Route } from "react-router-dom"; 3 | import Home from "./pages/Home"; 4 | import Login from "./pages/Login"; 5 | import Register from "./pages/Register"; 6 | import Profile from "./pages/Profile"; 7 | import CreatePost from "./pages/CreatePost"; 8 | import Comments from "./pages/Comment"; // ✅ Add this line! 9 | import Layout from "./components/Layout"; 10 | 11 | const App = () => { 12 | return ( 13 | 14 | 15 | } /> 16 | } /> 17 | } /> 18 | } /> 19 | 20 | {/* Login & Register don't use the main layout */} 21 | } /> 22 | } /> 23 | 24 | 25 | ); 26 | }; 27 | 28 | export default App; 29 | -------------------------------------------------------------------------------- /frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "frontend", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "vite build", 9 | "lint": "eslint .", 10 | "preview": "vite preview" 11 | }, 12 | "dependencies": { 13 | "@fontsource/inter": "^5.2.5", 14 | "@reduxjs/toolkit": "^2.6.1", 15 | "axios": "^1.8.4", 16 | "lucide-react": "^0.484.0", 17 | "react": "^19.0.0", 18 | "react-dom": "^19.0.0", 19 | "react-icons": "^5.5.0", 20 | "react-redux": "^9.2.0", 21 | "react-router-dom": "^7.4.0" 22 | }, 23 | "devDependencies": { 24 | "@eslint/js": "^9.21.0", 25 | "@tailwindcss/postcss": "^4.0.17", 26 | "@types/react": "^19.0.10", 27 | "@types/react-dom": "^19.0.4", 28 | "@vitejs/plugin-react": "^4.3.4", 29 | "autoprefixer": "^10.4.21", 30 | "eslint": "^9.21.0", 31 | "eslint-plugin-react-hooks": "^5.1.0", 32 | "eslint-plugin-react-refresh": "^0.4.19", 33 | "globals": "^15.15.0", 34 | "postcss": "^8.5.3", 35 | "tailwindcss": "^3.4.17", 36 | "vite": "^6.2.0" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /backend/public/css/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, 3 | "Helvetica Neue", Arial, sans-serif; 4 | line-height: 1.6; 5 | max-width: 1000px; 6 | margin: 0 auto; 7 | padding: 20px; 8 | } 9 | 10 | .api-title { 11 | color: #2c3e50; 12 | border-bottom: 2px solid #3498db; 13 | padding-bottom: 10px; 14 | } 15 | 16 | .endpoint { 17 | background: #f8f9fa; 18 | padding: 15px; 19 | margin: 10px 0; 20 | border-radius: 5px; 21 | box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); 22 | } 23 | 24 | .method { 25 | display: inline-block; 26 | padding: 4px 8px; 27 | border-radius: 4px; 28 | color: white; 29 | font-weight: bold; 30 | margin-right: 10px; 31 | } 32 | 33 | .get { 34 | background: #28a745; 35 | } 36 | .post { 37 | background: #007bff; 38 | } 39 | .put { 40 | background: #ffc107; 41 | color: #000; 42 | } 43 | .delete { 44 | background: #dc3545; 45 | } 46 | 47 | code { 48 | background: #e9ecef; 49 | padding: 2px 5px; 50 | border-radius: 3px; 51 | font-family: "Courier New", Courier, monospace; 52 | } 53 | 54 | pre { 55 | background: #f8f9fa; 56 | padding: 15px; 57 | border-radius: 5px; 58 | overflow-x: auto; 59 | } 60 | 61 | pre:hover { 62 | cursor: pointer; 63 | } -------------------------------------------------------------------------------- /frontend/src/components/Navbar.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Link, useLocation } from "react-router-dom"; 3 | import { Home, PlusCircle, User, LogIn } from "lucide-react"; 4 | 5 | const Navbar = () => { 6 | const location = useLocation(); 7 | 8 | const navItems = [ 9 | { to: "/", icon: , label: "Home" }, 10 | { to: "/create", icon: , label: "Create" }, 11 | { to: "/profile/1", icon: , label: "Profile" }, 12 | { to: "/login", icon: , label: "Login" }, 13 | ]; 14 | 15 | return ( 16 | 31 | ); 32 | }; 33 | 34 | export default Navbar; 35 | -------------------------------------------------------------------------------- /frontend/src/pages/Home.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from "react"; 2 | import PostCard from "../components/PostCard"; 3 | import API from "../api/axios"; 4 | 5 | const Home = () => { 6 | const [posts, setPosts] = useState([]); 7 | const [loading, setLoading] = useState(true); 8 | 9 | useEffect(() => { 10 | const fetchPosts = async () => { 11 | try { 12 | const res = await API.get("/post"); 13 | setPosts(Array.isArray(res.data) ? res.data : []); 14 | } catch (err) { 15 | console.error("Error fetching posts:", err); 16 | } finally { 17 | setLoading(false); 18 | } 19 | }; 20 | 21 | fetchPosts(); 22 | }, []); 23 | 24 | return ( 25 |
26 |

27 | Explore Creatify Feed 28 |

29 | 30 | {loading ? ( 31 |

Loading posts...

32 | ) : posts.length === 0 ? ( 33 |

No posts available.

34 | ) : ( 35 |
36 | {posts.map((post) => ( 37 | 38 | ))} 39 |
40 | )} 41 |
42 | ); 43 | }; 44 | 45 | export default Home; 46 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 🎨 Creatify - Social Media App 2 | 3 | Creatify is a full-stack social media web application — built for creators and visionaries. With a sleek modern UI and intuitive experience, users can share visual stories, like and comment on posts, and build their own digital presence. 4 | 5 | ## ✨ Features 6 | 7 | - 🏠 **Home Feed** – Scroll through all posts from users. 8 | - 🖼️ **Create & Upload Posts** – Share your creativity with captions and images. 9 | - ❤️ **Like Posts** – Includes toggle with heart animation. 10 | - 💬 **Comment System** – Add, edit, and delete comments. 11 | - ➕ **Follow/Unfollow** – Build and grow your audience. 12 | - ✏️ **Edit Captions** – Update or delete your own post captions. 13 | - 👤 **Profile Page** – View user details, avatar, bio, and their posts. 14 | - 📱 **Responsive Design** – Optimized for desktop-first; mobile ready. 15 | - ⚡ **Smooth UX** – Fast transitions and interactive feedback. 16 | 17 | ## 🛠️ Tech Stack 18 | 19 | ### 💻 Frontend 20 | - **React.js** with **React Router** 21 | - **Redux Toolkit** – Global state management 22 | - **Tailwind CSS** – Utility-first styling 23 | - **Axios** – API communication 24 | - **Lucide React** – Modern, lightweight UI icons 25 | 26 | ### 🔧 Backend 27 | - **Node.js** + **Express.js** – REST API 28 | - **MongoDB** with **Mongoose** – Database 29 | - **Firebase** – Image storage and retrieval 30 | 31 | ## 📁 Folder Structure (Simplified) 32 | -------------------------------------------------------------------------------- /frontend/public/vite.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /backend/index.js: -------------------------------------------------------------------------------- 1 | import express from "express"; 2 | import morgan from "morgan"; 3 | import helmet from "helmet"; 4 | import dotenv from "dotenv"; 5 | import cors from "cors"; 6 | import mongoose from "mongoose"; 7 | 8 | // Routers 9 | import { healthRouter } from "./routes/health.js"; 10 | import authRouter from "./routes/auth.js"; 11 | import postRouter from "./routes/post.js"; 12 | import userRouter from "./routes/user.js"; 13 | 14 | 15 | const app = express(); 16 | 17 | dotenv.config(); 18 | // console.log(process.env.MONGODB_URI); 19 | 20 | // Connect to MongoDB 21 | await mongoose 22 | .connect(process.env.MONGODB_URI) 23 | .then(() => console.log("Connected to MongoDB")) 24 | .catch((e) => console.error(e)); 25 | 26 | const PORT = process.env.PORT || 4000; 27 | 28 | 29 | 30 | // View Engine 31 | app.set("views", "./views"); 32 | app.set("view engine", "pug"); 33 | 34 | // Middlewares 35 | app.use(express.static("./public")); 36 | app.use(express.json()); 37 | app.use(express.urlencoded({ extended: true })); 38 | app.use(morgan("dev")); 39 | app.use(helmet()); 40 | app.use(cors()); 41 | 42 | // Routes 43 | app.get("/", (req, res) => { 44 | res.render("index"); 45 | }); 46 | 47 | // API Routes 48 | app.use("/api/health", healthRouter); 49 | app.use("/api/auth", authRouter); 50 | app.use("/api/post", postRouter); 51 | app.use("/api/user", userRouter); 52 | 53 | 54 | // Global error handling 55 | app.use((err, req, res, next) => { 56 | console.error(err); 57 | res.status(500).send("Seems like we messed up somewhere..."); 58 | }); 59 | 60 | app.listen(PORT, () => console.log(`Server is running on port: ${PORT}`)); 61 | -------------------------------------------------------------------------------- /backend/routes/auth.js: -------------------------------------------------------------------------------- 1 | import express from "express"; 2 | import bcrypt from "bcryptjs"; 3 | import jwt from "jsonwebtoken" 4 | import user from "../models/user.js" 5 | 6 | 7 | const router = express.Router(); 8 | 9 | // Register 10 | router.post("/register", async (req, res) => { 11 | try { 12 | const { username, email, password } = req.body; 13 | 14 | // Check if user exists 15 | const existingUser = await user.findOne({ email }); 16 | if (existingUser) return res.status(400).json({ message: "Email already exists" }); 17 | 18 | // Hash password 19 | const salt = await bcrypt.genSalt(10); 20 | const hashedPassword = await bcrypt.hash(password, salt); 21 | 22 | // Create new user 23 | const newUser = new user({ username, email, password: hashedPassword }); 24 | await newUser.save(); 25 | 26 | res.status(201).json({ message: "User registered successfully!" }); 27 | } catch (error) { 28 | res.status(500).json({ message: "Server error" }); 29 | } 30 | }); 31 | 32 | // Login 33 | router.post("/login", async (req, res) => { 34 | try { 35 | const { email, password } = req.body; 36 | 37 | const user = await user.findOne({ email }); 38 | if (!user) return res.status(400).json({ message: "Invalid credentials" }); 39 | 40 | const isMatch = await bcrypt.compare(password, user.password); 41 | if (!isMatch) return res.status(400).json({ message: "Invalid credentials" }); 42 | 43 | // Generate JWT Token 44 | const token = jwt.sign({ id: user._id }, process.env.JWT_SECRET, { expiresIn: "7d" }); 45 | 46 | res.json({ token, user }); 47 | } catch (error) { 48 | res.status(500).json({ message: "Server error" }); 49 | } 50 | }); 51 | 52 | export default router; -------------------------------------------------------------------------------- /backend/controllers/authController.js: -------------------------------------------------------------------------------- 1 | import bcrypt from ("bcryptjs"); 2 | import jwt from ("jsonwebtoken"); 3 | import User from ("../models/user"); 4 | 5 | // Register User 6 | exports.registerUser = async (req, res) => { 7 | try { 8 | const { username, email, password } = req.body; 9 | 10 | // Check if user already exists 11 | let user = await User.findOne({ email }); 12 | if (user) return res.status(400).json({ message: "User already exists" }); 13 | 14 | // Hash password 15 | const salt = await bcrypt.genSalt(10); 16 | const hashedPassword = await bcrypt.hash(password, salt); 17 | 18 | // Create new user 19 | user = new User({ username, email, password: hashedPassword }); 20 | await user.save(); 21 | 22 | // Generate JWT token 23 | const token = jwt.sign({ id: user._id }, process.env.JWT_SECRET, { expiresIn: "7d" }); 24 | 25 | res.status(201).json({ token, user }); 26 | } catch (error) { 27 | res.status(500).json({ message: "Server error" }); 28 | } 29 | }; 30 | 31 | // Login User 32 | exports.loginUser = async (req, res) => { 33 | try { 34 | const { email, password } = req.body; 35 | 36 | // Check if user exists 37 | const user = await User.findOne({ email }); 38 | if (!user) return res.status(400).json({ message: "Invalid credentials" }); 39 | 40 | // Validate password 41 | const isMatch = await bcrypt.compare(password, user.password); 42 | if (!isMatch) return res.status(400).json({ message: "Invalid credentials" }); 43 | 44 | // Generate token 45 | const token = jwt.sign({ id: user._id }, process.env.JWT_SECRET, { expiresIn: "7d" }); 46 | 47 | res.status(200).json({ token, user }); 48 | } catch (error) { 49 | res.status(500).json({ message: "Server error" }); 50 | } 51 | }; 52 | -------------------------------------------------------------------------------- /frontend/src/components/PostCard.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import { Heart, MessageCircle } from "lucide-react"; 3 | import CommentBox from "./CommentBox"; 4 | 5 | const PostCard = ({ post }) => { 6 | const { userAvatar, username, image, caption, likes: initialLikes, comments } = post; 7 | 8 | const [likes, setLikes] = useState(initialLikes); 9 | const [liked, setLiked] = useState(false); 10 | const [showComments, setShowComments] = useState(false); 11 | 12 | const toggleLike = () => { 13 | setLiked(!liked); 14 | setLikes((prev) => (liked ? prev - 1 : prev + 1)); 15 | }; 16 | 17 | return ( 18 |
19 |
20 | {username} 21 | {username} 22 |
23 | post 24 | 25 |
26 |
27 | 32 | 35 |
36 | 37 |

38 | {username} {caption} 39 |

40 |

{likes} likes

41 | 42 | {showComments && ( 43 |
44 | 45 |
46 | )} 47 |
48 |
49 | ); 50 | }; 51 | 52 | export default PostCard; 53 | -------------------------------------------------------------------------------- /backend/controllers/postController.js: -------------------------------------------------------------------------------- 1 | import Post from "../models/post"; 2 | 3 | // Create Post 4 | exports.createPost = async (req, res) => { 5 | try { 6 | const { caption, imageUrl } = req.body; 7 | const newPost = new Post({ 8 | user: req.user.id, 9 | caption, 10 | imageUrl, 11 | }); 12 | 13 | await newPost.save(); 14 | res.status(201).json(newPost); 15 | } catch (error) { 16 | res.status(500).json({ message: "Server error" }); 17 | } 18 | }; 19 | 20 | // Get All Posts 21 | exports.getPosts = async (req, res) => { 22 | try { 23 | const posts = await Post.find().populate("user", "username profilePic").sort({ createdAt: -1 }); 24 | res.status(200).json(posts); 25 | } catch (error) { 26 | res.status(500).json({ message: "Server error" }); 27 | } 28 | }; 29 | 30 | // Like a Post 31 | exports.likePost = async (req, res) => { 32 | try { 33 | const post = await Post.findById(req.params.id); 34 | if (!post) return res.status(404).json({ message: "Post not found" }); 35 | 36 | if (post.likes.includes(req.user.id)) { 37 | post.likes = post.likes.filter(id => id.toString() !== req.user.id.toString()); 38 | } else { 39 | post.likes.push(req.user.id); 40 | } 41 | 42 | await post.save(); 43 | res.status(200).json(post); 44 | } catch (error) { 45 | res.status(500).json({ message: "Server error" }); 46 | } 47 | }; 48 | 49 | // Comment on a Post 50 | exports.commentOnPost = async (req, res) => { 51 | try { 52 | const { text } = req.body; 53 | const post = await Post.findById(req.params.id); 54 | if (!post) return res.status(404).json({ message: "Post not found" }); 55 | 56 | const comment = { user: req.user.id, text, createdAt: new Date() }; 57 | post.comments.push(comment); 58 | 59 | await post.save(); 60 | res.status(200).json(post); 61 | } catch (error) { 62 | res.status(500).json({ message: "Server error" }); 63 | } 64 | }; 65 | -------------------------------------------------------------------------------- /backend/controllers/userController.js: -------------------------------------------------------------------------------- 1 | import User from ("../models/user"); 2 | 3 | // Get User Profile 4 | exports.getUserProfile = async (req, res) => { 5 | try { 6 | const user = await User.findById(req.params.id).select("-password"); 7 | if (!user) return res.status(404).json({ message: "User not found" }); 8 | 9 | res.status(200).json(user); 10 | } catch (error) { 11 | res.status(500).json({ message: "Server error" }); 12 | } 13 | }; 14 | 15 | // Follow a User 16 | exports.followUser = async (req, res) => { 17 | try { 18 | const user = await User.findById(req.params.id); 19 | const currentUser = await User.findById(req.user.id); 20 | 21 | if (!user || !currentUser) return res.status(404).json({ message: "User not found" }); 22 | if (currentUser.following.includes(user._id)) return res.status(400).json({ message: "Already following" }); 23 | 24 | currentUser.following.push(user._id); 25 | user.followers.push(currentUser._id); 26 | 27 | await currentUser.save(); 28 | await user.save(); 29 | 30 | res.status(200).json({ message: "Followed successfully" }); 31 | } catch (error) { 32 | res.status(500).json({ message: "Server error" }); 33 | } 34 | }; 35 | 36 | // Unfollow a User 37 | exports.unfollowUser = async (req, res) => { 38 | try { 39 | const user = await User.findById(req.params.id); 40 | const currentUser = await User.findById(req.user.id); 41 | 42 | if (!user || !currentUser) return res.status(404).json({ message: "User not found" }); 43 | if (!currentUser.following.includes(user._id)) return res.status(400).json({ message: "Not following this user" }); 44 | 45 | currentUser.following = currentUser.following.filter(id => id.toString() !== user._id.toString()); 46 | user.followers = user.followers.filter(id => id.toString() !== currentUser._id.toString()); 47 | 48 | await currentUser.save(); 49 | await user.save(); 50 | 51 | res.status(200).json({ message: "Unfollowed successfully" }); 52 | } catch (error) { 53 | res.status(500).json({ message: "Server error" }); 54 | } 55 | }; 56 | -------------------------------------------------------------------------------- /frontend/src/pages/CreatePost.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import { useNavigate } from "react-router-dom"; 3 | 4 | const CreatePost = () => { 5 | const navigate = useNavigate(); 6 | const [formData, setFormData] = useState({ 7 | caption: "", 8 | image: null, 9 | }); 10 | 11 | const handleChange = (e) => { 12 | const { name, value, files } = e.target; 13 | if (name === "image") { 14 | setFormData({ ...formData, image: files[0] }); 15 | } else { 16 | setFormData({ ...formData, [name]: value }); 17 | } 18 | }; 19 | 20 | const handleSubmit = (e) => { 21 | e.preventDefault(); 22 | // Here you'd send to backend, or Firebase, etc. 23 | console.log("New Post Submitted:", formData); 24 | navigate("/"); // go back to home/feed 25 | }; 26 | 27 | return ( 28 |
29 |
30 |

31 | Create a New Post 🖼️ 32 |

33 |
34 |
35 | 36 |