Building a Real-Time Collaborative Notepad with Socket.IO and MongoDB
Ever needed to quickly share notes with someone but got frustrated with sign-up forms and complex permission settings? That's exactly why I built Collaborative Notepad.
🎯 The Problem
Most note-taking apps are either:
- Too complex (Google Docs, Notion)
- Require accounts and logins
- Don't offer real-time collaboration
- Not open source
I wanted something dead simple: Create a note → Share the link → Collaborate instantly.
✨ What I Built
Collaborative Notepad is a free, open-source real-time notepad where:
- ✅ No sign-up required
- ✅ Real-time collaboration
- ✅ Custom URLs (e.g.,
/meeting-notes) - ✅ Dark/Light mode
- ✅ Auto-save every 3 seconds
- ✅ Shows live user count
Live Demo: https://collabnote.link
GitHub: https://github.com/sh13y/collaborative-notepad
🛠️ Tech Stack
- Backend: Node.js + Express
- Real-time: Socket.IO
- Database: MongoDB
- Template Engine: EJS
- Security: Helmet, Rate Limiting, CSP
🏗️ Architecture Overview
1. Server Setup (Express + Socket.IO)
const express = require('express');
const http = require('http');
const socketIo = require('socket.io');
const mongoose = require('mongoose');
const app = express();
const server = http.createServer(app);
const io = socketIo(server);
// MongoDB connection
mongoose.connect(process.env.MONGODB_URI);
// Socket.IO connection handling
io.on('connection', (socket) => {
console.log('User connected');
socket.on('join-note', async (noteId) => {
socket.join(noteId);
// Broadcast user count
io.to(noteId).emit('user-count',
io.sockets.adapter.rooms.get(noteId)?.size || 0
);
});
socket.on('note-update', async ({ noteId, content }) => {
// Save to database
await Note.findOneAndUpdate(
{ noteId },
{ content, lastModified: Date.now() }
);
// Broadcast to all users in room
socket.to(noteId).emit('note-change', content);
});
});
2. MongoDB Schema
const noteSchema = new mongoose.Schema({
noteId: { type: String, unique: true, required: true },
content: { type: String, default: '' },
createdAt: { type: Date, default: Date.now },
lastModified: { type: Date, default: Date.now },
viewCount: { type: Number, default: 0 }
});
const Note = mongoose.model('Note', noteSchema);
3. Client-Side Real-Time Sync
const socket = io();
const noteId = window.location.pathname.substring(1) || 'home';
// Join note room
socket.emit('join-note', noteId);
// Send updates to server (with debouncing)
let timeout;
textarea.addEventListener('input', () => {
clearTimeout(timeout);
timeout = setTimeout(() => {
socket.emit('note-update', {
noteId,
content: textarea.value
});
}, 300);
});
// Receive updates from other users
socket.on('note-change', (content) => {
if (document.activeElement !== textarea) {
textarea.value = content;
}
});
// Update user count
socket.on('user-count', (count) => {
userCountElement.textContent = count;
});
🔒 Security Measures
1. Rate Limiting
const rateLimit = require('express-rate-limit');
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100 // 100 requests per window
});
app.use('/api/', limiter);
2. Content Security Policy
const helmet = require('helmet');
app.use(helmet.contentSecurityPolicy({
directives: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'", "'unsafe-inline'"],
styleSrc: ["'self'", "'unsafe-inline'"]
}
}));
3. XSS Prevention
const xss = require('xss');
// Sanitize user input
const sanitizedContent = xss(userInput);
🚀 Key Features Implementation
Custom URLs
Users can create memorable note URLs:
https://collabnote.link/team-meeting
https://collabnote.link/project-ideas
Implemented with Express routing:
app.get('/:noteId', async (req, res) => {
const noteId = req.params.noteId;
const note = await Note.findOne({ noteId }) ||
await Note.create({ noteId });
res.render('index', { note });
});
Auto-Save
Saves every 3 seconds when content changes:
let saveTimeout;
const AUTO_SAVE_DELAY = 3000;
textarea.addEventListener('input', () => {
clearTimeout(saveTimeout);
saveTimeout = setTimeout(saveToDatabase, AUTO_SAVE_DELAY);
});
Dark Mode
CSS custom properties for easy theme switching:
:root {
--bg-color: #fefae0;
--text-color: #1e3a1f;
}
[data-theme="dark"] {
--bg-color: #1e1e1e;
--text-color: #e0e0e0;
}
📊 Performance Optimizations
- Debouncing Socket Emissions - Reduces server load
- MongoDB Indexing - Fast note lookups
- Connection Pooling - Efficient database connections
- Compression - Gzip middleware for faster loads
🎓 What I Learned
- Socket.IO Rooms - Perfect for multi-user collaboration
- Debouncing - Essential for real-time typing
- MongoDB Optimization - Indexing makes huge difference
- Security - Never trust user input
- UX Matters - Simple is better than feature-rich
🐛 Challenges Faced
Challenge 1: Cursor Position
When receiving updates, cursor would jump to end. Solution: Only update if textarea not focused.
Challenge 2: Race Conditions
Multiple users typing simultaneously caused conflicts. Solution: Implement debouncing and last-write-wins strategy.
Challenge 3: Memory Leaks
Socket connections weren't cleaning up. Solution: Proper disconnect handling.
🔮 Future Plans
- [ ] Markdown support
- [ ] Syntax highlighting for code
- [ ] Password-protected notes
- [ ] Export to PDF/TXT
- [ ] Collaborative cursor positions
- [ ] Voice notes
- [ ] Mobile app
💡 Try It Yourself
Live Demo: https://collabnote.link
Source Code: https://github.com/sh13y/collaborative-notepad
It's MIT licensed - feel free to fork, modify, and learn from it!
🙏 Feedback Welcome
This is my first major open-source project. I'd love to hear:
- Feature suggestions
- Bug reports
- Code review feedback
- General thoughts
Drop a comment below or open an issue on GitHub!
Tech Stack Summary: Node.js • Express • Socket.IO • MongoDB • EJS
Development Time: ~20 hours
License: MIT (Open Source)
If you found this helpful, give it a ❤️ and star the GitHub repo!
Top comments (0)