In this updated tutorial, we’ll build a real-time chat app using the MEVN stack (MongoDB, Express, Vue 3, Node.js) and Socket.IO v4—all with the latest versions and best practices, including ES Modules, Composition API, and Vite.
🧠 Project Overview
This app will allow users to:
-
Choose a username.
-
Send and receive messages in real-time.
-
View chat history from MongoDB.
-
See live messages through WebSockets.
⚙️ Prerequisites
-
Node.js ≥ 20.x
-
npm or pnpm
-
MongoDB (local or Atlas)
-
Basic knowledge of Vue 3 and JavaScript
1. Backend Setup (Node.js + Express)
📁 Project Structure
server/
├── models/
│ └── Message.js
├── routes/
│ └── messages.js
├── app.js
├── socket.js
├── package.json
📦 Initialize Project
mkdir server && cd server
npm init -y
🛠 Install Dependencies
npm install express mongoose socket.io cors dotenv
npm install --save-dev nodemon
Optional: add "type": "module"
to package.json
for ESM
"type": "module"
🧱 Create app.js
import express from 'express';
import http from 'http';
import cors from 'cors';
import mongoose from 'mongoose';
import dotenv from 'dotenv';
import { Server as SocketIOServer } from 'socket.io';
import messageRoutes from './routes/messages.js';
import Message from './models/Message.js';
dotenv.config();
const app = express();
const server = http.createServer(app);
const io = new SocketIOServer(server, {
cors: {
origin: 'http://localhost:5173', // Vite port
methods: ['GET', 'POST']
}
});
// Middleware
app.use(cors());
app.use(express.json());
app.use('/api/messages', messageRoutes);
// Socket.IO
io.on('connection', (socket) => {
console.log('A user connected:', socket.id);
socket.on('chat message', async (msg) => {
const message = new Message({ username: msg.username, text: msg.text });
await message.save();
io.emit('chat message', message); // broadcast to all
});
socket.on('disconnect', () => {
console.log('User disconnected:', socket.id);
});
});
// MongoDB connection
mongoose.connect(process.env.MONGO_URI || 'mongodb://localhost/chatdb')
.then(() => console.log('MongoDB connected'))
.catch(console.error);
const PORT = process.env.PORT || 3000;
server.listen(PORT, () => console.log(`Server running on http://localhost:${PORT}`));
🧾 .env
MONGO_URI=mongodb://localhost:27017/chatdb
📄 Mongoose Model: models/Message.js
import mongoose from 'mongoose';
const MessageSchema = new mongoose.Schema({
username: String,
text: String,
createdAt: {
type: Date,
default: Date.now
}
});
export default mongoose.model('Message', MessageSchema);
📄 Routes: routes/messages.js
import express from 'express';
import Message from '../models/Message.js';
const router = express.Router();
router.get('/', async (req, res) => {
const messages = await Message.find().sort({ createdAt: 1 }).limit(100);
res.json(messages);
});
export default router;
✅ That wraps up the backend setup.
2. Frontend Setup (Vue 3 + Vite)
We'll build the chat UI using Vue 3, Composition API, and Vite for fast development and hot module reload.
📁 Frontend Structure
client/
├── src/
│ ├── components/
│ │ └── ChatApp.vue
│ ├── App.vue
│ └── main.js
├── index.html
├── vite.config.js
└── package.json
🧱 Initialize Vue 3 Project with Vite
npm create vite@latest client -- --template vue
cd client
npm install
🛠 Install Dependencies
npm install axios socket.io-client
🔌 Configure Vite (optional CORS fix if needed): vite.config.js
import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
export default defineConfig({
plugins: [vue()],
server: {
port: 5173,
},
});
✍️ src/main.js
import { createApp } from 'vue';
import App from './App.vue';
import './style.css';
createApp(App).mount('#app');
🧩 src/App.vue
<template>
<main class="min-h-screen flex items-center justify-center bg-gray-100">
<ChatApp />
</main>
</template>
<script setup>
import ChatApp from './components/ChatApp.vue';
</script>
💬 src/components/ChatApp.vue
<template>
<div class="w-full max-w-md mx-auto bg-white p-4 rounded shadow">
<h2 class="text-xl font-semibold mb-2">Vue 3 Chat</h2>
<div class="h-64 overflow-y-auto border p-2 mb-2 bg-gray-50">
<div v-for="msg in messages" :key="msg._id" class="mb-1">
<strong>{{ msg.username }}:</strong> {{ msg.text }}
</div>
</div>
<form @submit.prevent="sendMessage" class="flex gap-2">
<input
v-model="text"
placeholder="Type message..."
class="flex-1 border rounded px-2 py-1"
/>
<button class="bg-blue-600 text-white px-3 py-1 rounded">Send</button>
</form>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue';
import { io } from 'socket.io-client';
import axios from 'axios';
const socket = io('http://localhost:3000');
const username = prompt('Enter your name') || 'Anonymous';
const text = ref('');
const messages = ref([]);
// Fetch history
onMounted(async () => {
const res = await axios.get('http://localhost:3000/api/messages');
messages.value = res.data;
socket.on('chat message', (msg) => {
messages.value.push(msg);
});
});
function sendMessage() {
if (!text.value.trim()) return;
socket.emit('chat message', { username, text: text.value });
text.value = '';
}
</script>
Do the same way with `routes/chat.js`. Now, run the server using this command.
npm start
You will see the previous Vue.js landing page when you point your browser to `http://localhost:3000`. When you change the address to `http://localhost:3000/api/room` or `http://localhost:3000/api/chat`, you will see this page.
3. Run Both Servers
1. Start the backend from /server
:
nodemon app.js
2. Start the frontend from /client
:
npm run dev
Open: http://localhost:5173
4. Testing the App
-
Open the app in multiple tabs or browsers.
-
Enter different usernames.
-
Send messages—see them appear in real-time via Socket.IO.
-
Refresh the page—past messages are loaded from MongoDB.
5. Conclusion
In this updated 2025 MEVN + Socket.IO tutorial, you built a real-time chat app from scratch using the latest stack:
-
Node.js 20+ and Express 4.x for the backend API and Socket.IO server
-
MongoDB 7+ with Mongoose 8+ for message persistence
-
Vue 3 (Composition API) powered by Vite for a modern frontend experience
-
Socket.IO 4.x for low-latency, bi-directional real-time communication
This modernized version replaces Vue CLI with Vite, uses native ES modules, and includes localStorage-friendly improvements like username prompts and improved styling with Tailwind utilities (if desired).
With this foundation, you can:
-
Add user authentication
-
Group chats or private messages
-
Store sessions or use JWTs
-
Deploy on platforms like Vercel, Render, or Heroku
You can find the full working source code on our GitHub.
That's just the basics. If you need more deep learning about MEVN Stack, Vue.js, or related, you can take the following cheap course:
- Isomorphic JavaScript with MEVN Stack
- Fun Projects with Vue 2
- Learning Path: Vue. js: Rapid Web Development with Vue 2
- Getting Started with Vue JS 2
- Vue. js 2 Recipes
Thanks!