DEV Community

Cover image for Building a Real-Time Collaborative Notepad with Socket.IO and MongoDB
shiey
shiey

Posted on

Building a Real-Time Collaborative Notepad with Socket.IO and MongoDB

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);
  });
});
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

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;
});
Enter fullscreen mode Exit fullscreen mode

🔒 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);
Enter fullscreen mode Exit fullscreen mode

2. Content Security Policy

const helmet = require('helmet');

app.use(helmet.contentSecurityPolicy({
  directives: {
    defaultSrc: ["'self'"],
    scriptSrc: ["'self'", "'unsafe-inline'"],
    styleSrc: ["'self'", "'unsafe-inline'"]
  }
}));
Enter fullscreen mode Exit fullscreen mode

3. XSS Prevention

const xss = require('xss');

// Sanitize user input
const sanitizedContent = xss(userInput);
Enter fullscreen mode Exit fullscreen mode

🚀 Key Features Implementation

Custom URLs

Users can create memorable note URLs:

https://collabnote.link/team-meeting
https://collabnote.link/project-ideas
Enter fullscreen mode Exit fullscreen mode

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 });
});
Enter fullscreen mode Exit fullscreen mode

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);
});
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

📊 Performance Optimizations

  1. Debouncing Socket Emissions - Reduces server load
  2. MongoDB Indexing - Fast note lookups
  3. Connection Pooling - Efficient database connections
  4. Compression - Gzip middleware for faster loads

🎓 What I Learned

  1. Socket.IO Rooms - Perfect for multi-user collaboration
  2. Debouncing - Essential for real-time typing
  3. MongoDB Optimization - Indexing makes huge difference
  4. Security - Never trust user input
  5. 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)