A real-time collaborative drawing application inspired by Excalidraw. This project is a learning exercise focused on understanding modern full-stack architecture, real-time communication, and monorepo management.
Work In Progress - Backend prototype complete, frontend development starting soon.
This is primarily a learning project aimed at mastering:
- Monorepo architecture with Turborepo
- Real-time WebSocket communication
- Advanced TypeScript patterns
- Database management with Prisma
- Security best practices
- Full-stack application development
- Shared Packages: Global packages accessible across all apps
- Turbo Caching: Optimized build times through intelligent caching
- Type Safety: Global TypeScript definitions shared across workspace
Backend:
- Node.js + Express
- WebSocket (ws library)
- Prisma ORM
- PostgreSQL
- JWT Authentication
Frontend: (Coming Soon)
- NextJS
- TypeScript
- Canvas API
DevOps:
- Turborepo
- TypeScript
- Docker (planned)
- WebSocket server for persistent connections
- Room-based collaboration (join/leave rooms)
- Real-time shape and drawing data transport
- Conflict resolution for simultaneous edits
- JWT token-based authentication
- HTTP server for WebSocket upgrade handshake
- Cookie security (HttpOnly, Secure flags)
- XSS protection
- CSRF protection
- CORS configuration
- Prisma schema design
- Migration management
- User and Room models
- Many-to-many relationships for room participants
- How turbo caching works under the hood
- Creating and managing shared packages
- Configuring pipeline tasks and dependencies
- Optimizing build performance
- Complex type definitions and generics
- Global type declarations
- Type safety across monorepo packages
- Solving intricate type errors
- Real-time bidirectional communication
- Room-based architecture
- State synchronization across clients
- Connection lifecycle management
- Challenge: WebSocket protocol doesn't support headers like HTTP
- Solution: Use HTTP server for initial upgrade request
- Client sends JWT in cookie during HTTP handshake
- Server verifies JWT before upgrading to WebSocket
- Authenticated user attached to WebSocket connection
- Schema versioning and evolution
- Generating and applying migrations
- Handling schema changes in development vs production
- Database relationship modeling
- XSS Prevention: Sanitizing user inputs, HttpOnly cookies
- CSRF Protection: Token-based validation
- Secure Cookies: Secure flag for HTTPS-only transmission
- CORS: (Upcoming) Cross-origin resource sharing policies
excalidraw-clone/
โโโ apps/
โ โโโ web/ # Frontend React app (coming soon)
โ โโโ ws-server/ # WebSocket server
โ โโโ src/
โ โ โโโ index.ts # Server entry point
โ โ โโโ handlers/ # WebSocket event handlers
โ โโโ package.json
โโโ packages/
โ โโโ db/ # Shared Prisma client
โ โ โโโ prisma/
โ โ โ โโโ schema.prisma
โ โ โโโ index.ts
โ โโโ types/ # Shared TypeScript types
โ โโโ config/ # Shared configurations
โโโ turbo.json # Turborepo configuration
โโโ package.json # Root package.json
- Node.js (v18 or higher)
- PostgreSQL
- pnpm (recommended) or npm
- Clone the repository
git clone https://github.com/yourusername/excalidraw-clone.git
cd excalidraw-clone- Install dependencies
pnpm install- Set up environment variables
cp .env.example .env
# Edit .env with your configurationRequired environment variables:
DATABASE_URL="postgresql://user:password@localhost:5432/excalidraw"
JWT_SECRET="your-super-secret-jwt-key"
PORT=8080
- Run Prisma migrations
pnpm db:migrate- Start the development server
pnpm dev// Client connects with JWT in cookie
const ws = new WebSocket('ws://localhost:8080');Join Room
{
"type": "join_room",
"roomId": "room-uuid"
}Leave Room
{
"type": "leave_room",
"roomId": "room-uuid"
}Send Drawing Data (Coming Soon)
{
"type": "draw",
"roomId": "room-uuid",
"data": {
"shapes": [...],
"action": "add|update|delete"
}
}model User {
id String @id @default(uuid())
email String @unique
name String
password String
photo String?
rooms Room[] @relation("RoomParticipants")
}model Room {
id String @id @default(uuid())
name String
roomParticipants User[] @relation("RoomParticipants")
createdAt DateTime @default(now())
}- Frontend canvas implementation
- Shape rendering and manipulation
- Color picker and drawing tools
- User presence indicators
- Drawing history and undo/redo
- Export drawings (PNG, SVG)
- Collaborative cursor positions
- Room persistence and sharing
- Docker deployment setup
- Performance optimization
- WebSocket reconnection logic not implemented
- No rate limiting on WebSocket messages
- Database connection pooling needs optimization
- Type definitions need cleanup in some areas
This project pushed me way outside my comfort zone. Here are the biggest takeaways:
-
TypeScript is your friend: Those red squiggly lines were frustrating at first, but they saved me from countless runtime errors.
-
Monorepos are powerful: Once you understand turbo caching and shared packages, development speed increases dramatically.
-
WebSocket authentication is tricky: You can't just slap a JWT in the connection. The HTTP upgrade handshake pattern is the way to go.
-
Security matters from day one: Implementing XSS and CSRF protection early is much easier than retrofitting it later.
-
Real projects beat tutorials: I learned more debugging WebSocket connections than I did from any course.
This is primarily a learning project, but suggestions and feedback are welcome! Feel free to:
- Open issues for bugs or questions
- Submit PRs for improvements
- Share your own learnings
MIT License - feel free to use this for your own learning!
- Inspired by Excalidraw
- Built while learning from the developer community
Note: This is a work-in-progress learning project, not production-ready software. Use at your own risk!
Feel free to reach out if you have questions or want to discuss real-time application architecture!
- GitHub: @sakshamVerma08
- LinkedIn: [Your LinkedIn Profile]
โญ If this project helps you learn, consider giving it a star!