A real-time collaborative code editing platform built with React, Monaco/CodeMirror, and Yjs.
- 🔐 User authentication (register/login)
- 🏠 Room management (create, join, list rooms)
- 👥 Multi-user real-time collaboration
- 🎨 Syntax highlighting for multiple languages
- 👁️ See other users' cursors and selections in real-time
- 🔄 Follow mode - follow another user's viewport
- 💾 Persistent document storage with PostgreSQL
- 🔒 Room-based access control (including strict session locking for ended rooms)
- 🔑 Fine-grained room permissions (read/write/delete all) with superuser, admin, and user roles
- 🔗 Guest share links with configurable view/edit permissions
- React 19 + TypeScript
- Vite (with Rolldown)
- CodeMirror 6
- Yjs (CRDT for collaboration)
- Hocuspocus Provider (WebSocket client)
- React Router for navigation
- Rust (Tokio)
- Axum (REST API)
- Custom Yjs sync server (yrs)
- SQLx
- PostgreSQL database
- JWT authentication
- bcrypt for password hashing
- Bun installed (frontend)
- Rust toolchain (backend)
- PostgreSQL database running
- Docker and Docker Compose installed
The easiest way to run ShareCode is using Docker Compose:
# Clone the repository
git clone <repository-url>
cd sharecode
# Start all services (PostgreSQL, server, frontend)
docker compose up -d
# View logs
docker compose logs -f
# Stop all services
docker compose downThe application will be available at http://localhost
Architecture:
- All requests go through nginx reverse proxy on port 80
- nginx routes
/api/*requests to the backend server (port 3001) - nginx serves frontend static files for other paths
- WebSocket connections use
/api/ws
Default Superuser Credentials:
- Username:
admin - Password:
admin123 - Email:
admin@sharecode.local
docker-compose.yml before deploying to production! This account boots as the first superuser and can manage every other account.
Edit docker-compose.yml to customize the server environment:
environment:
DATABASE_URL: postgresql://sharecode_app:sharecode@postgres:5432/sharecode?schema=public
JWT_SECRET: change-me-in-production # ⚠️ Change this!
PORT: 3001
LOG_LEVEL: info # Options: debug, info, warn, error
# Superuser credentials - CHANGE THESE IN PRODUCTION
ADMIN_USERNAME: admin
ADMIN_PASSWORD: admin123
ADMIN_EMAIL: admin@sharecode.local
# ADMIN_UPDATE_PASSWORD: true # Uncomment to update password on restartAfter code changes, rebuild the images:
# Rebuild and restart all services
docker compose up -d --build
# Rebuild only specific service
docker compose build server
docker compose up -d server# Enter PostgreSQL container
docker compose exec postgres psql -U sharecode_app -d sharecodeWith just, Docker Compose, Rust, Bun, and Node.js installed:
just install
just up # Start PostgreSQL, the API, and the frontend in one terminalFor separate terminals, use just server and just dev instead. just up
uses concurrently to stream labeled application logs and stop both process trees on Ctrl+C or if
either application exits. PostgreSQL stays running; use just db-stop to stop it.
Open http://localhost:5173. On a fresh development database, sign in with
local_admin / LocalTest#2026Strong. Override these local defaults using
SHARECODE_DEV_ADMIN_USERNAME, SHARECODE_DEV_ADMIN_PASSWORD, and
SHARECODE_DEV_DATABASE_URL. The database URL defaults to the credentials in
docker-compose.dev.yml. just does not load .env files; the server still reads
server-rs/.env for settings not explicitly supplied by the recipe.
just # List commands
just browsers # Install test browsers and system dependencies
just test-ui # Chromium UI tests; no backend required
just test-ui firefox # Firefox UI tests
just test-ui-all # Chromium, Firefox, and WebKit
just test-editor # Collaboration and Markdown regression tests
just test-i18n # Translation keys, parameters, and untranslated labels
just test-server # Rust unit tests
just check # TypeScript and Rust checks
just build # Production frontend build
just db-stop # Stop PostgreSQL and keep its dataStop the API and frontend with Ctrl+C in their terminals. UI test screenshots
are written to /tmp/sharecode-ui-review/<browser>/. For the full disposable
database integration suite, use just test-integration; it requires local
PostgreSQL server binaries (initdb, pg_ctl, and createdb) and cached Rust
dependencies from just install. Set PG_BIN if the PostgreSQL binaries are
outside the paths detected by the script.
Alternatively, start each service manually:
Make sure PostgreSQL is running. Update the connection string in server-rs/.env:
DATABASE_URL="postgresql://postgres:password@localhost:5432/sharecode?schema=public"cd server-rs
# Build (optional)
cargo build
# Start the server
cargo runThe server will start on port 3001:
- REST API at
/api/* - WebSocket server at
/api/ws
cd frontend
# Install dependencies
bun install
# Start dev server
bun run devThe frontend will be available at http://localhost:5173
For local development, create server-rs/.env with the following values:
# Database
DATABASE_URL="postgresql://sharecode_app:sharecode@localhost:5432/sharecode?schema=public"
# JWT Secret - CHANGE THIS IN PRODUCTION
JWT_SECRET="your-super-secret-jwt-key-change-in-production"
# Server
PORT=3001
# Frontend URL (for CORS)
FRONTEND_URL="http://localhost:5173"
# App URL (optional, for absolute links)
APP_URL="http://localhost:5173"
# WebSocket router mode for desktop apps (HashRouter)
FRONTEND_HASH_ROUTER=false
# Logging - Options: debug, info, warn, error (default: info)
LOG_LEVEL="info"
# Allow registration (default true)
ALLOW_REGISTRATION=true
# Code execution (Piston)
PISTON_URL="http://localhost:2000"
# Superuser credentials - CHANGE THESE IN PRODUCTION
ADMIN_USERNAME="admin"
ADMIN_PASSWORD="admin123"
ADMIN_EMAIL="admin@sharecode.local"
# Set to 'true' to update admin password on restart (optional)
# ADMIN_UPDATE_PASSWORD="false"# For local development without Docker (separate ports)
VITE_API_URL=http://localhost:3001
VITE_WS_URL=ws://localhost:3001/api/ws
# For Docker deployment - comment out the above and nginx will handle routing
# VITE_API_URL=
# VITE_WS_URL=Note: The WebSocket server runs on the same port as the REST API at path /api/ws.
POST /api/auth/register- Register new userPOST /api/auth/login- LoginGET /api/auth/profile- Get user profile (authenticated)
POST /api/rooms- Create new room (authenticated)GET /api/rooms- Get user's rooms (authenticated)GET /api/rooms/:roomId- Get room details (authenticated)PUT /api/rooms/:roomId- Update room (owner or users with write-all permission)POST /api/rooms/:roomId/end- End an active room (owner or users with delete-all permission)DELETE /api/rooms/:roomId- Delete room (owner or users with delete-all permission)POST /api/rooms/:roomId/join- Join room (authenticated)POST /api/rooms/:roomId/leave- Leave room (authenticated)POST /api/rooms/:roomId/share-links- Create a guest share link with view/edit permissions (owner)GET /api/rooms/:roomId/share-links- List share links for the room (owner)DELETE /api/rooms/:roomId/share-links/:shareLinkId- Delete an existing share link (owner)
GET /api/share/:token- Inspect share link details for a guest invitePOST /api/share/:token/join- Join a room as a guest by providing display name/emailGET /api/share/session- Refresh guest session details using the guest token
GET /api/admin/users- List all active users (admin/superuser)POST /api/admin/users- Create a new user with role + global permissions (admin limited to normal users, superuser for all)PATCH /api/admin/users/:id- Update role or global permissionsDELETE /api/admin/users/:id- Soft delete a user (admins can only remove normal users; superusers can delete admins; superusers cannot be deleted)
GET /api/admin/rooms- List all roomsDELETE /api/admin/rooms/:id- Force-delete a roomGET /api/admin/storage/db-size- Database size summaryGET /api/admin/storage/playback- Playback storage per roomPOST /api/admin/rooms/:id/playback/compress- Compress playback to 1s resolution
The system combines a role hierarchy with three global room flags:
canReadAllRoomscanWriteAllRoomscanDeleteAllRooms
canDeleteAllRooms implies room-management authority such as ending/deleting rooms across the system. In the admin UI, superusers can edit all role and global-permission assignments.
| Capability | User | Admin | Superuser |
|---|---|---|---|
| View own accessible rooms | Yes | Yes | Yes |
| Create rooms | Yes | Yes | Yes |
| Join allowed rooms | Yes | Yes | Yes |
| Read any room | No* | Yes | Yes |
| Edit any room | No* | Yes | Yes |
| View all users in admin panel | No | Yes | Yes |
| Create normal users | No | Yes | Yes |
| Create admins or superusers | No | No | Yes |
| Edit normal users | No | Yes | Yes |
| Edit admins | No | No | Yes |
| Edit superusers | No | No | Yes |
| Delete normal users | No | Yes | Yes |
| Delete admins | No | No | Yes |
| Delete superusers | No | No | No |
| View all rooms in admin panel | No | No | Yes |
| Force-delete any room | No | No | Yes |
| View storage / playback admin pages | No | No | Yes |
* Normal users can get these capabilities via the global flags below.
Global room flags apply on top of role and let you grant room-wide access without promoting a user to admin:
| Global Flag | Effect |
|---|---|
canReadAllRooms |
User can see and open every room, even if not explicitly allowed. |
canWriteAllRooms |
User can edit every room (implies canReadAllRooms). |
canDeleteAllRooms |
User can end or delete every room (implies canWriteAllRooms and canReadAllRooms). |
Role ↔ global-flag interaction. Admin and superuser roles implicitly have all three global-room capabilities (canReadAllRooms / canWriteAllRooms / canDeleteAllRooms), regardless of whether those flags are set on their account. The flags exist to grant the same room-wide access to normal users. This is enforced identically on the REST and WebSocket paths — see core::permissions::role_has_global_{read,write,delete}.
Notes:
- Room owners can manage their own rooms regardless of role.
- Admins are still restricted to user-level account management; they cannot manage admins or superusers.
- Superusers can delete admins, including the last remaining admin.
- Superusers cannot delete themselves, and superuser accounts are protected from deletion.
- JavaScript/TypeScript
- Python
- Java
- C/C++
- Rust
- Go
- PHP
sharecode/
├── notes/ # Learning notes
│ ├── codemirror.md # CodeMirror + Yjs integration guide
│ └── hocuspocus.md # Hocuspocus server guide
├── frontend/ # React frontend
│ ├── src/
│ │ ├── components/ # React components
│ │ ├── hooks/ # Custom hooks
│ │ ├── lib/ # Utilities and API client
│ │ ├── App.tsx
│ │ └── main.tsx
│ └── package.json
├── server-rs/ # Backend server (Rust/Axum)
│ ├── src/
│ │ ├── routes/ # REST API routes
│ │ ├── ws/ # WebSocket protocol + sync
│ │ ├── core/ # Auth/config/state
│ │ └── main.rs
│ └── Cargo.toml
└── README.md
- Y.Doc: Each document is represented as a Yjs document (Y.Doc)
- HocuspocusProvider: Connects to the WebSocket server and syncs the Y.Doc
- CodeMirror Binding: y-codemirror.next binds the editor to the Y.Doc
- Awareness: Tracks user presence (cursor position, selection, user info)
- Database: Documents are persisted as binary (Uint8Array) in PostgreSQL
Architecture Note: As of November 2025, room.id (UUID) serves as both the database primary key and the Yjs document identifier. This unified architecture simplifies routing and eliminates the need for separate documentId tracking. When you create a room, its UUID becomes the WebSocket document name for collaboration.
- User registers/logs in via REST API
- Server returns JWT token
- Token is stored in localStorage
- Token is passed to the WebSocket server for authentication
- Token is included in REST API requests
- Room owner has full control over their room (edit, delete, manage participants)
- Participants can edit if
canEditis true, otherwise they are read-only - Optional fine-grained permissions allow any user to be granted:
- Read all rooms — view every active/ended room
- Write all rooms — edit any room (implies read-all)
- Delete all rooms — delete or end any room (implies read/write)
- Three role tiers ship out-of-the-box:
- Superuser — manages admins, toggles all permissions, full room control
- Admin — manages regular users and can write all rooms
- User — controls personal rooms and any room the owner explicitly shares
- Users must be authenticated to access rooms and their permission flags are validated on every REST/WebSocket request
- The WebSocket server verifies access on every connection and auto-adds participants with read-only or read/write access based on their permissions
MIT
For local development, run just runner-up once to install the required runtimes, then use just up as usual. Runtime packages persist across container restarts. just test-runner tests execution and stdin for Python, Java, C, C++, JavaScript and TypeScript, error/timeout handling, and administrator room-language permissions with a disposable local database.
For production, from the deployment checkout:
docker compose -f docker-compose.production.yml --env-file .env.production up -d piston
docker compose -f docker-compose.production.yml --env-file .env.production exec -T piston node < scripts/setup-piston.cjsThe installer is idempotent and uses the persistent Piston packages volume. An empty Piston container does not include language runtimes. /api/code/health returns 503 if any required language is missing; /api/code/languages lists installed languages. Piston's package-management API must remain internal to the deployment network.