A production-grade, thread-based conversational AI chatbot built from scratch with the MERN stack — not just another ChatGPT wrapper.
- What is StackMind?
- Core Features
- Tech Stack
- System Architecture
- Authentication Flow
- Backend Architecture
- Frontend Architecture
- Installation Guide
- Docker Deployment
- Project Structure
- API Routes Documentation
- Performance Optimizations
- Future Roadmap
- Performance Metrics
- Security Practices
- Contributing
- FAQ
- Author & Support
StackMind is an enterprise-grade, full-stack conversational AI chatbot built using the MERN Stack (MongoDB, Express.js, React.js, Node.js) and powered by the Google Gemini 2.5 Flash API.
| Problem | StackMind Solution |
|---|---|
| Generic ChatGPT wrappers | Custom-built from the ground up for real use cases |
| No chat history management | Thread-based MongoDB architecture with message tracking |
| Poor UI/UX | 40ms word-by-word typing effect for a human-like feel |
| Difficult to scale | RESTful API design ready for Docker, K8s, and microservices |
| Scattered codebase | Clean separation: Models → Utils → Routes |
- 🧵 Thread-Based Chat Management — each conversation is an independent, timestamped thread with auto-generated titles
- 📝 Rich Message Formatting — full Markdown rendering + syntax-highlighted code blocks (
rehype-highlight, multiple themes) - ⌨️ Human-Like Typing Animation — word-by-word streaming effect at 40ms intervals
- 🔐 JWT Authentication — signup/login with hashed passwords and ownership-based access control
- 🔌 RESTful API — clean
/api/threadand/api/chatendpoints - 🤝 Multi-LLM Ready — Gemini 2.5 Flash primary, OpenAI fallback, extensible to Claude/Grok/LLaMA
| Technology | Purpose | Version |
|---|---|---|
| React.js | UI framework | 18.x+ |
| Vite | Build tool | 4.x+ |
| Context API | State management | Built-in |
| react-markdown | Markdown rendering | Latest |
| rehype-highlight | Code syntax highlighting | Latest |
| uuid | Unique thread IDs | Latest |
| Tailwind / Custom CSS | Styling | — |
| Font Awesome | Icons | 7.0.1+ |
| Technology | Purpose | Details |
|---|---|---|
| Node.js | Runtime | LTS recommended |
| Express.js | Web framework | 4.x+ |
| MongoDB | Database | Atlas or local |
| Mongoose | ODM | Schema validation |
| dotenv | Env config | Secure API keys |
| jsonwebtoken | Auth | JWT signing/verification |
| bcryptjs | Password hashing | Salted hashes |
| Google Gemini API | Primary AI model | gemini-2.5-flash |
| OpenAI API | Fallback AI | gpt-4o-mini |
Docker · Nginx · Docker Compose · Kubernetes · GitHub Actions CI/CD
flowchart TB
subgraph Frontend["Frontend (React + Vite)"]
Sidebar["Sidebar<br/>(Thread History)"]
ChatWindow["ChatWindow<br/>(Messages)"]
NavBar["NavBar<br/>(User Dropdown)"]
end
subgraph Backend["Backend (Node.js + Express)"]
Routes["Routes<br/>/api/chat /api/thread"]
Middleware["authMiddleware.js<br/>(JWT Verify)"]
Utils["Utils<br/>geminiai.js / openai.js"]
Models["Models<br/>ThreadSchema / User"]
end
Gemini[("Google Gemini API")]
Mongo[("MongoDB Atlas")]
Sidebar --> Routes
ChatWindow --> Routes
NavBar --> Routes
Routes --> Middleware
Middleware --> Utils
Middleware --> Models
Utils --> Gemini
Models --> Mongo
The production-style local deployment uses two containers. The frontend image builds the React application with Vite and serves the generated static files through Nginx. Nginx also reverse-proxies /api/* requests to the Express backend over the internal Docker network.
MongoDB Atlas and the Gemini API remain external managed services. They are accessed by the backend through environment variables and are not included in the Docker images.
flowchart LR
Browser["Browser<br/>http://localhost:5173"]
subgraph Docker["Docker Compose Network"]
Frontend["Frontend container<br/>Nginx + React dist<br/>Port 80"]
Backend["Backend container<br/>Node.js + Express<br/>Port 8080"]
end
Mongo[("MongoDB Atlas")]
Gemini[("Google Gemini API")]
Browser --> Frontend
Frontend -->|"/api/* proxy"| Backend
Backend --> Mongo
Backend --> Gemini
- The browser loads the React application from Nginx on port
5173. - React sends relative requests such as
/api/auth/login,/api/thread, and/api/chat. - Nginx forwards those requests to the
backendCompose service on port8080. - Express authenticates the request and executes the route logic.
- Mongoose reads and writes user data in MongoDB Atlas.
- The chat route calls the Gemini API and returns the assistant response.
sequenceDiagram
participant U as User
participant F as Frontend (LoginForm.jsx)
participant A as Auth Routes (auth.js)
participant M as authMiddleware.js
participant C as Chat Routes (chat.js)
participant DB as MongoDB
U->>F: Enter email + password
F->>A: POST /api/auth/signup or /login
A->>DB: Check user, hash/verify password (bcrypt)
DB-->>A: User record
A-->>F: Signed JWT token
F->>F: Store token (localStorage)
F->>M: Request to /api/thread or /api/chat (Bearer token)
M->>M: Verify JWT
M->>C: Attach req.user, forward request
C->>DB: Query filtered by req.user.userId
DB-->>C: User-owned thread data only
C-->>F: JSON response
Why this matters: every thread is linked to a userId, and every read/delete/write is filtered by req.user.userId — so one user can never access or hijack another user's chat thread.
ThreadSchema
{
threadId: String, // unique, generated via uuid
userId: ObjectId, // owner reference
title: String, // auto-generated from first message
messages: [MessageSchema],
createdAt: Date,
updatedAt: Date
}MessageSchema
{
content: String, // message text
role: "user" | "assistant",
timestamp: Date
}| Component | Responsibility |
|---|---|
App.jsx |
Root component, holds global state (prompt, reply, threads, auth) |
Sidebar.jsx |
Thread list, "New Chat" button, delete action |
ChatWindow.jsx |
Hosts NavBar + Chat + InputBox |
Chat.jsx |
Renders messages, drives the typing animation |
MyContext.jsx |
Context API provider shared across the app |
Typing animation (word-by-word, 40ms):
const content = reply.split(" ");
let idx = 0;
const interval = setInterval(() => {
setLatestReply(content.slice(0, idx + 1).join(" "));
idx++;
if (idx >= content.length) clearInterval(interval);
}, 40);- Node.js v14+
- npm or yarn
- MongoDB (local or Atlas)
- Google Gemini API key
git clone https://github.com/vivekkr620/StackMind.git
cd StackMindcd Backend
npm install
# create .env
cat > .env << EOF
MONGODB_URL=your_mongodb_connection_string
GEMINI_API_KEY=your_gemini_api_key_here
OPENAI_API_KEY=your_openai_api_key_here # optional
JWT_SECRET=your_jwt_secret
PORT=8080
EOF
npm start
# or, for auto-reload during development
npx nodemon server.jscd Frontend
npm install
npm run dev# Backend → http://localhost:8080
curl http://localhost:8080/api/thread
# Frontend → http://localhost:5173This repository includes a production-style Docker Compose deployment with separate frontend and backend containers. The frontend is compiled once with Vite and served by Nginx. The backend runs as a Node.js process and connects to MongoDB Atlas and the Google Gemini API.
- Docker Desktop with Docker Compose enabled
- A MongoDB Atlas connection string
- A Google Gemini API key
- A strong JWT signing secret
- MongoDB Atlas network access configured for the machine or server running the backend
Create Backend/.env locally. Do not commit this file or copy it into a Docker image.
MONGODB_URL=your_mongodb_connection_string
GEMINI_API_KEY=your_gemini_api_key
JWT_SECRET=your_long_random_secret
PORT=8080The Compose file passes this file to the backend container at runtime. The frontend does not need a secret file because its API requests use relative /api paths and are routed by Nginx.
| File | Responsibility |
|---|---|
docker-compose.yml |
Defines the frontend and backend services, networking, ports, and startup policy |
Backend/Dockerfile |
Builds the Node.js backend image and runs npm start |
Backend/.dockerignore |
Excludes node_modules, .env, and logs from the backend image context |
Frontend/Dockerfile |
Builds the Vite application and serves it from an Nginx image |
Frontend/.dockerignore |
Excludes local dependencies and generated files from the frontend build context |
Frontend/nginx.conf |
Serves React files and proxies /api/* requests to the backend service |
Run these commands from the repository root, where docker-compose.yml is located:
# Validate the Compose configuration
docker compose config
# Build both images
docker compose build
# Start the application in the background
docker compose up -d
# Confirm both services are running
docker compose psOpen the application at:
http://localhost:5173
Only the frontend port is published to the host. The backend port 8080 is available to the frontend container through the internal Compose network as http://backend:8080.
After changing application code, Dockerfiles, or Nginx configuration:
docker compose up -d --buildFor a clean dependency rebuild:
docker compose build --no-cache
docker compose up -d# View service status
docker compose ps
# View backend logs
docker compose logs backend
# View frontend logs
docker compose logs frontend
# Follow all logs
docker compose logs -fVerify the application by signing up, logging in, sending a chat message, loading a previous thread, and deleting a thread. These workflows verify the complete path through Nginx, Express, MongoDB Atlas, and Gemini.
# Stop and remove containers and the Compose network
docker compose down
# Stop, remove containers, and remove locally built images
docker compose down --rmi localThe deployment does not use a local MongoDB container. User data remains in MongoDB Atlas when the application containers are stopped or rebuilt.
- Rotate any credentials that have been exposed or committed during development.
- Store production secrets in the deployment platform's secret manager instead of a repository file.
- Restrict MongoDB Atlas network access to trusted server addresses.
- Use HTTPS at the infrastructure layer before exposing the application publicly.
- Restrict CORS to the production frontend origin instead of allowing every origin.
- Add rate limiting, request validation, structured logging, and a health endpoint before public deployment.
- Scan and regularly rebuild images to receive current base-image security updates.
StackMind/
├── Backend/
├── middleware/
│ └── authMiddleware.js
├── models/
│ ├── Thread.js
│ └── User.js
├── routes/
│ ├── auth.js
│ └── chat.js
├── utils/
│ └── geminiai.js
├── server.js
└── package.json
├── Frontend/
├── public/
├── src/
│ ├── assets/
│ ├── components/
│ │ └── LoginForm.jsx
│ ├── App.css
│ ├── App.jsx
│ ├── Chat.css
│ ├── Chat.jsx
│ ├── ChatWindow.css
│ ├── ChatWindow.jsx
│ ├── index.css
│ ├── main.jsx
│ ├── MyContext.jsx
│ ├── Sidebar.css
│ └── Sidebar.jsx
├── index.html
├── vite.config.js
├── eslint.config.js
└── package.json
│
└── README.md
Base URL: http://localhost:8080/api
| Method | Route | Auth | Description |
|---|---|---|---|
GET |
/thread |
✅ | List all threads for the logged-in user, sorted by updatedAt desc |
GET |
/thread/:threadId |
✅ | Fetch all messages in a specific thread |
POST |
/chat |
✅ | Send a message, get an AI reply, persist both |
DELETE |
/thread/:threadId |
✅ | Delete a thread (ownership-checked) |
Example — POST /chat
// Request
{
"threadId": "abc-123-def",
"message": "What is React?"
}
// Response (200)
{
"reply": "React is a JavaScript library for building user interfaces..."
}
// Response (403) — cross-user hijack attempt
{
"error": "You are not allowed to access this chat"
}Frontend: lazy-loaded sidebar history · debounced thread search · React.memo() on heavy components · code-splitting
Backend: indexed threadId lookups · pagination on thread lists · MongoDB Atlas connection pooling · gzip response compression
| Endpoint | Typical Response Time |
|---|---|
GET /thread |
~100–150ms (1000 threads) |
GET /thread/:threadId |
~50–80ms |
POST /chat |
~2–3s (Gemini API latency) |
DELETE /thread/:threadId |
~100–120ms |
- JWT authentication
- OAuth login (Google/GitHub)
- Voice input via Whisper API
- Dark/Light theme toggle
- Full mobile responsiveness
- Full-text thread search
- Rate limiting
- Docker deployment
- Kubernetes deployment
- Redis caching layer
- Multi-LLM fallback router (Claude/Grok)
- Streaming responses via SSE
- RAG with custom documents
- File upload & document processing
| Metric | Target | Current |
|---|---|---|
| Time to First Byte | < 200ms | ~150ms |
| First Contentful Paint | < 1.5s | ~1.2s |
| Largest Contentful Paint | < 2.5s | ~2.0s |
| Cumulative Layout Shift | < 0.1 | ~0.05 |
| Time to Interactive | < 3.5s | ~3.0s |
- ✅ Environment variables for all secrets
- ✅ CORS restricted to frontend origin
- ✅ Server-side input validation
- ✅ Generic error responses (no stack traces leaked)
- ✅ Mongoose schema validation (injection prevention)
- ✅ Password hashing with bcrypt + salt
- ✅ Ownership checks on every thread operation
- ⏳ Rate limiting (planned)
# 1. Fork & clone
git clone https://github.com/vivekkr620/StackMind.git
# 2. Create a feature branch
git checkout -b feature/amazing-feature
# 3. Commit your changes
git commit -m "Add amazing feature"
# 4. Push and open a PR
git push origin feature/amazing-featurePlease follow existing code style, use meaningful variable names, and test before submitting a PR.
Can I use StackMind in production? Yes — it follows production patterns: input validation, error handling, and environment-based config.
How much does it cost to run? Google Gemini has a generous free tier; check Google AI Studio for current pricing.
Can I deploy to AWS/Azure? Yes, once Docker/K8s support lands (see roadmap) — until then, any Node-compatible host works.
What happens if I hit Gemini's rate limit? The planned multi-LLM router will automatically fall back to another model.
Vivek Kumar — Full-Stack Developer, MERN Stack & AI Integrations
Found a bug or have a feature idea? Open a GitHub Issue or start a Discussion.
⭐ If this project helped you, consider giving it a star! ⭐
Made with ❤️ by Vivek Kumar
