Build a ChatGPT Clone using MERN Stack

Build a ChatGPT Clone using MERN Stack

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: 'https://updategadh.com/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 Restaurant Management System Using React, Next.js

Step 4 — Start the Backend

cd backend
node server.js

Step 5 — Start the Frontend

cd frontend
npm run dev

Open https://updategadh.com 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 Health Insurance Management System MERN Stack

🛒 Visit UpdateGadh Store →
💬 Chat Now