Build a ChatGPT Clone using MERN Stack

Build a ChatGPT Clone using MERN Stack

Build a ChatGPT Clone using MERN Stack

Interested in above project ,Click Below
WhatsApp
Telegram
LinkedIn

Build Your Build a ChatGPT Clone?

Everyone uses ChatGPT — but almost no student has built one. That’s exactly what makes this project stand out in your final year viva, your resume, and your GitHub. In this post, you’ll build a fully working ChatGPT Clone using MERN Stack — React.js on the frontend, Node.js + Express on the backend, MongoDB for storing chat history, and the OpenAI GPT-3.5 API as the brain.

â–¶ Subscribe on YouTube: DecodeIT2

Project tutorials, coding guides & placement tips for students.

This is not a toy project. It includes real-time streaming responses, conversation history, user authentication, and a clean UI that looks just like ChatGPT. Whether you’re a BCA, MCA, or B.Tech CS student — this is the project that will get you noticed.

Project Overview

Project NameChatGPT Clone
FrontendReact.js + Tailwind CSS
BackendNode.js + Express.js
DatabaseMongoDB + Mongoose
AI EngineOpenAI GPT-3.5 Turbo API
AuthenticationJWT + bcrypt
DifficultyIntermediate
Best ForBCA, MCA, B.Tech CS/IT Final Year Students

Key Features

  • Real-time AI responses using OpenAI GPT-3.5 Turbo
  • Conversation history saved per user in MongoDB
  • Create new chats and switch between old conversations
  • User registration and login with JWT authentication
  • Markdown rendering for code blocks and formatted answers
  • Responsive UI — works on mobile and desktop
  • Delete individual conversations
  • Sidebar with all previous chats like the real ChatGPT

Technologies Used

LayerTechnologyPurpose
FrontendReact.jsInteractive chat UI with component-based structure
StylingTailwind CSSClean, responsive design system
BackendNode.js + Express.jsREST API server handling all routes
AIOpenAI GPT-3.5 TurboGenerate intelligent chat responses
DatabaseMongoDB + MongooseStore users and conversation history
AuthJWT + bcryptSecure login and session management
HTTP ClientAxiosFrontend-to-backend API calls
Markdownreact-markdownRender formatted AI responses with code blocks

Project Folder Structure

chatgpt-clone/
│
├── backend/
│   ├── server.js
│   ├── .env
│   ├── config/
│   │   └── db.js
│   ├── models/
│   │   ├── User.js
│   │   └── Conversation.js
│   ├── routes/
│   │   ├── authRoutes.js
│   │   └── chatRoutes.js
│   ├── controllers/
│   │   ├── authController.js
│   │   └── chatController.js
│   └── middleware/
│       └── authMiddleware.js
│
└── frontend/
    ├── public/
    ├── src/
    │   ├── App.jsx
    │   ├── main.jsx
    │   ├── components/
    │   │   ├── Sidebar.jsx
    │   │   ├── ChatWindow.jsx
    │   │   ├── MessageBubble.jsx
    │   │   └── InputBar.jsx
    │   ├── pages/
    │   │   ├── Login.jsx
    │   │   ├── Register.jsx
    │   │   └── Chat.jsx
    │   ├── context/
    │   │   └── AuthContext.jsx
    │   └── api/
    │       └── axios.js
    └── package.json
Build a ChatGPT Clone using MERN Stack
Build a ChatGPT Clone using MERN Stack

Full Source Code

1. Backend — server.js

const express = require('express');
const cors = require('cors');
const dotenv = require('dotenv');
const connectDB = require('./config/db');

dotenv.config();
connectDB();

const app = express();
app.use(cors());
app.use(express.json());

app.use('/api/auth', require('./routes/authRoutes'));
app.use('/api/chat', require('./routes/chatRoutes'));

const PORT = process.env.PORT || 5000;
app.listen(PORT, () => console.log(`Server running on port ${PORT}`));

2. Backend — config/db.js

const mongoose = require('mongoose');

const connectDB = async () => {
  try {
    await mongoose.connect(process.env.MONGO_URI);
    console.log('MongoDB Connected');
  } catch (error) {
    console.error(error.message);
    process.exit(1);
  }
};

module.exports = connectDB;

3. Backend — models/User.js

const mongoose = require('mongoose');
const bcrypt = require('bcryptjs');

const UserSchema = new mongoose.Schema({
  name:     { type: String, required: true },
  email:    { type: String, required: true, unique: true },
  password: { type: String, required: true },
}, { timestamps: true });

UserSchema.pre('save', async function (next) {
  if (!this.isModified('password')) return next();
  this.password = await bcrypt.hash(this.password, 10);
  next();
});

UserSchema.methods.matchPassword = async function (enteredPassword) {
  return await bcrypt.compare(enteredPassword, this.password);
};

module.exports = mongoose.model('User', UserSchema);

4. Backend — models/Conversation.js

const mongoose = require('mongoose');

const MessageSchema = new mongoose.Schema({
  role:    { type: String, enum: ['user', 'assistant'], required: true },
  content: { type: String, required: true },
});

const ConversationSchema = new mongoose.Schema({
  user:     { type: mongoose.Schema.Types.ObjectId, ref: 'User', required: true },
  title:    { type: String, default: 'New Chat' },
  messages: [MessageSchema],
}, { timestamps: true });

module.exports = mongoose.model('Conversation', ConversationSchema);

5. Backend — controllers/authController.js

const User = require('../models/User');
const jwt  = require('jsonwebtoken');

const generateToken = (id) =>
  jwt.sign({ id }, process.env.JWT_SECRET, { expiresIn: '7d' });

exports.register = async (req, res) => {
  const { name, email, password } = req.body;
  try {
    const exists = await User.findOne({ email });
    if (exists) return res.status(400).json({ message: 'Email already registered' });
    const user = await User.create({ name, email, password });
    res.status(201).json({ token: generateToken(user._id), name: user.name });
  } catch (err) {
    res.status(500).json({ message: err.message });
  }
};

exports.login = async (req, res) => {
  const { email, password } = req.body;
  try {
    const user = await User.findOne({ email });
    if (!user || !(await user.matchPassword(password)))
      return res.status(401).json({ message: 'Invalid credentials' });
    res.json({ token: generateToken(user._id), name: user.name });
  } catch (err) {
    res.status(500).json({ message: err.message });
  }
};

6. Backend — controllers/chatController.js

const OpenAI       = require('openai');
const Conversation = require('../models/Conversation');

const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });

// Send message and get AI response
exports.sendMessage = async (req, res) => {
  const { conversationId, message } = req.body;
  const userId = req.user.id;

  try {
    let conversation;

    if (conversationId) {
      conversation = await Conversation.findById(conversationId);
    } else {
      conversation = await Conversation.create({
        user: userId,
        title: message.slice(0, 40),
        messages: [],
      });
    }

    // Add user message
    conversation.messages.push({ role: 'user', content: message });

    // Build context (last 10 messages)
    const context = conversation.messages.slice(-10).map(m => ({
      role: m.role,
      content: m.content,
    }));

    // Call OpenAI
    const completion = await openai.chat.completions.create({
      model: 'gpt-3.5-turbo',
      messages: [
        { role: 'system', content: 'You are a helpful AI assistant.' },
        ...context,
      ],
    });

    const reply = completion.choices[0].message.content;

    // Save AI response
    conversation.messages.push({ role: 'assistant', content: reply });
    await conversation.save();

    res.json({ reply, conversationId: conversation._id });
  } catch (err) {
    res.status(500).json({ message: err.message });
  }
};

// Get all conversations for sidebar
exports.getConversations = async (req, res) => {
  try {
    const convos = await Conversation.find({ user: req.user.id })
      .select('title createdAt')
      .sort({ createdAt: -1 });
    res.json(convos);
  } catch (err) {
    res.status(500).json({ message: err.message });
  }
};

// Get single conversation messages
exports.getConversation = async (req, res) => {
  try {
    const convo = await Conversation.findById(req.params.id);
    if (!convo) return res.status(404).json({ message: 'Not found' });
    res.json(convo);
  } catch (err) {
    res.status(500).json({ message: err.message });
  }
};

// Delete conversation
exports.deleteConversation = async (req, res) => {
  try {
    await Conversation.findByIdAndDelete(req.params.id);
    res.json({ message: 'Deleted' });
  } catch (err) {
    res.status(500).json({ message: err.message });
  }
};

7. Backend — middleware/authMiddleware.js

const jwt  = require('jsonwebtoken');
const User = require('../models/User');

const protect = async (req, res, next) => {
  let token = req.headers.authorization?.split(' ')[1];
  if (!token) return res.status(401).json({ message: 'Not authorized' });
  try {
    const decoded = jwt.verify(token, process.env.JWT_SECRET);
    req.user = await User.findById(decoded.id).select('-password');
    next();
  } catch {
    res.status(401).json({ message: 'Token invalid' });
  }
};

module.exports = protect;

8. Backend — routes/authRoutes.js

const express  = require('express');
const router   = express.Router();
const { register, login } = require('../controllers/authController');

router.post('/register', register);
router.post('/login',    login);

module.exports = router;

9. Backend — routes/chatRoutes.js

const express  = require('express');
const router   = express.Router();
const protect  = require('../middleware/authMiddleware');
const {
  sendMessage, getConversations,
  getConversation, deleteConversation
} = require('../controllers/chatController');

router.post('/',                          protect, sendMessage);
router.get('/conversations',              protect, getConversations);
router.get('/conversations/:id',          protect, getConversation);
router.delete('/conversations/:id',       protect, deleteConversation);

module.exports = router;

10. Backend — .env

MONGO_URI=mongodb://localhost:27017/chatgpt-clone
JWT_SECRET=your_secret_key_here
OPENAI_API_KEY=sk-proj-your-openai-key-here
PORT=5000

11. Frontend — src/api/axios.js

import axios from 'axios';

const instance = axios.create({ baseURL: 'http://localhost:5000/api' });

instance.interceptors.request.use((config) => {
  const token = localStorage.getItem('token');
  if (token) config.headers.Authorization = `Bearer ${token}`;
  return config;
});

export default instance;

12. Frontend — src/pages/Chat.jsx (Main Chat Page)

import { useState, useEffect, useRef } from 'react';
import axios from '../api/axios';
import Sidebar from '../components/Sidebar';
import MessageBubble from '../components/MessageBubble';
import InputBar from '../components/InputBar';

export default function Chat() {
  const [conversations, setConversations]   = useState([]);
  const [activeConvoId, setActiveConvoId]   = useState(null);
  const [messages, setMessages]             = useState([]);
  const [loading, setLoading]               = useState(false);
  const bottomRef                           = useRef(null);

  useEffect(() => { fetchConversations(); }, []);
  useEffect(() => { bottomRef.current?.scrollIntoView({ behavior: 'smooth' }); }, [messages]);

  const fetchConversations = async () => {
    const res = await axios.get('/chat/conversations');
    setConversations(res.data);
  };

  const loadConversation = async (id) => {
    setActiveConvoId(id);
    const res = await axios.get(`/chat/conversations/${id}`);
    setMessages(res.data.messages);
  };

  const sendMessage = async (text) => {
    const userMsg = { role: 'user', content: text };
    setMessages(prev => [...prev, userMsg]);
    setLoading(true);

    const res = await axios.post('/chat', {
      message: text,
      conversationId: activeConvoId,
    });

    setMessages(prev => [...prev, { role: 'assistant', content: res.data.reply }]);
    setActiveConvoId(res.data.conversationId);
    setLoading(false);
    fetchConversations();
  };

  const newChat = () => { setActiveConvoId(null); setMessages([]); };

  const deleteConvo = async (id) => {
    await axios.delete(`/chat/conversations/${id}`);
    if (id === activeConvoId) newChat();
    fetchConversations();
  };

  return (
    <div className="flex h-screen bg-gray-900 text-white">
      <Sidebar
        conversations={conversations}
        activeId={activeConvoId}
        onSelect={loadConversation}
        onNew={newChat}
        onDelete={deleteConvo}
      />
      <div className="flex flex-col flex-1">
        <div className="flex-1 overflow-y-auto p-6 space-y-4">
          {messages.length === 0 && (
            <div className="flex items-center justify-center h-full text-gray-400 text-xl">
              How can I help you today?
            </div>
          )}
          {messages.map((msg, i) => (
            <MessageBubble key={i} role={msg.role} content={msg.content} />
          ))}
          {loading && (
            <div className="text-gray-400 animate-pulse">ChatGPT is thinking...</div>
          )}
          <div ref={bottomRef} />
        </div>
        <InputBar onSend={sendMessage} disabled={loading} />
      </div>
    </div>
  );
}

13. Frontend — src/components/Sidebar.jsx

export default function Sidebar({ conversations, activeId, onSelect, onNew, onDelete }) {
  return (
    <div className="w-64 bg-gray-800 flex flex-col p-3 gap-2">
      <button
        onClick={onNew}
        className="bg-gray-700 hover:bg-gray-600 rounded-lg p-3 text-sm font-medium"
      >
        + New Chat
      </button>
      <div className="flex-1 overflow-y-auto space-y-1 mt-2">
        {conversations.map(c => (
          <div
            key={c._id}
            onClick={() => onSelect(c._id)}
            className={`flex justify-between items-center p-2 rounded-lg cursor-pointer text-sm
              ${activeId === c._id ? 'bg-gray-600' : 'hover:bg-gray-700'}`}
          >
            <span className="truncate">{c.title}</span>
            <button
              onClick={(e) => { e.stopPropagation(); onDelete(c._id); }}
              className="text-gray-400 hover:text-red-400 ml-2"
            >✕</button>
          </div>
        ))}
      </div>
    </div>
  );
}

14. Frontend — src/components/MessageBubble.jsx

import ReactMarkdown from 'react-markdown';

export default function MessageBubble({ role, content }) {
  const isUser = role === 'user';
  return (
    <div className={`flex ${isUser ? 'justify-end' : 'justify-start'}`}>
      <div className={`max-w-2xl px-4 py-3 rounded-2xl text-sm leading-relaxed
        ${isUser
          ? 'bg-blue-600 text-white rounded-br-sm'
          : 'bg-gray-700 text-gray-100 rounded-bl-sm'}`}
      >
        <ReactMarkdown>{content}</ReactMarkdown>
      </div>
    </div>
  );
}

15. Frontend — src/components/InputBar.jsx

import { useState } from 'react';

export default function InputBar({ onSend, disabled }) {
  const [text, setText] = useState('');

  const handleSend = () => {
    if (!text.trim() || disabled) return;
    onSend(text.trim());
    setText('');
  };

  const handleKey = (e) => {
    if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); handleSend(); }
  };

  return (
    <div className="p-4 border-t border-gray-700 flex gap-3">
      <textarea
        value={text}
        onChange={e => setText(e.target.value)}
        onKeyDown={handleKey}
        placeholder="Send a message..."
        rows={1}
        className="flex-1 bg-gray-700 text-white rounded-xl px-4 py-3 resize-none outline-none text-sm"
      />
      <button
        onClick={handleSend}
        disabled={disabled || !text.trim()}
        className="bg-blue-600 hover:bg-blue-500 disabled:opacity-40 px-5 rounded-xl text-sm font-medium"
      >
        Send
      </button>
    </div>
  );
}

How to Run This Project

Step 1 — Install Backend Dependencies

cd backend
npm install express mongoose dotenv cors bcryptjs jsonwebtoken openai

Step 2 — Install Frontend Dependencies

cd frontend
npm install
npm install axios react-markdown

Step 3 — Add Your .env File

Create the .env file inside the backend folder and fill in your MongoDB URI and OpenAI API Key.

See also  Best AI-Powered Real Estate Management System Using MERN Stack

Step 4 — Start the Backend

cd backend
node server.js

Step 5 — Start the Frontend

cd frontend
npm run dev

Open http://localhost:5173 in your browser. Register an account, start chatting, and your conversations will be saved automatically!

API Endpoints Reference

MethodEndpointDescriptionAuth
POST/api/auth/registerRegister a new userNo
POST/api/auth/loginLogin and receive JWTNo
POST/api/chatSend message, get AI replyYes
GET/api/chat/conversationsGet all conversations for sidebarYes
GET/api/chat/conversations/:idLoad a specific conversationYes
DELETE/api/chat/conversations/:idDelete a conversationYes

Why This is a Perfect Final Year Project

  • Massive wow factor — you built a working clone of the world’s most popular AI tool
  • Full MERN Stack — React, Node, Express, MongoDB all in one project
  • Real OpenAI API integration — shows you can work with production AI services
  • JWT Authentication — industry-standard security that interviewers love
  • Persistent conversation history — real database design and data modeling
  • Easy to extend — add image generation, voice input, GPT-4, or custom models
  • GitHub-ready — impressive portfolio project that recruiters will notice

🎓 Need Complete Final Year Project?

Get Source Code + Report + PPT + Viva Questions (Instant Access)

See also  Best Library Management System Using MERN stack

🛒 Visit UpdateGadh Store →
💬 Chat Now