Skip to content

Latest commit

 

History

22 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 

Repository files navigation

CodeLink

Real-time collaborative code editor — register, create a room with a shared password, and start coding together instantly in the browser.


Table of Contents

  1. Overview
  2. Tech Stack
  3. Project Structure
  4. Getting Started
  5. REST API Endpoints
  6. WebSocket Events
  7. Application Workflow
  8. Load Testing Results

Overview

CodeLink is a full-stack collaborative coding platform where multiple users can edit the same code in real time. Authentication is JWT-based; rooms are protected by a shared password. Live code synchronisation is handled over WebSocket (STOMP over SockJS), while room metadata is persisted in PostgreSQL and the active code buffer is cached in Redis for sub-millisecond reads.

Key Features

Feature Details
🔐 Authentication JWT-secured register / login — no email required
🚪 Room creation Any authenticated user creates a password-protected room
⚡ Live code sync STOMP over SockJS; edits broadcast to all subscribers in < 100 ms
💾 Dual-layer persistence Redis (live buffer) + PostgreSQL (durable store)
🧹 Viewer presence Real-time viewer count pushed on connect / disconnect
🗂 Multi-language editor Monaco Editor with syntax highlighting
🌓 Dark / light theme Persistent theme preference

Tech Stack

Backend — CodeLink-backend/

Layer Technology
Runtime Java 21
Framework Spring Boot 3.4.2
Security Spring Security + JJWT 0.12.6 (JWT)
Real-time Spring WebSocket (STOMP broker)
ORM Spring Data JPA / Hibernate
Database PostgreSQL 14+
Cache Redis (Upstash, SSL) via Spring Data Redis
Build Maven 3.9+ / Maven Wrapper
Container Docker (multi-stage Dockerfile)

Frontend — CodeLink-frontend/

Layer Technology
Framework React 19 + Vite 8
Routing React Router DOM v7
Editor Monaco Editor (@monaco-editor/react)
WebSocket STOMP.js + SockJS-client
Icons Lucide React
Styling Tailwind CSS v4
Linting OxLint

Project Structure

CodeLink/
├── CodeLink-backend/
│   ├── src/main/java/org/code/codelink/
│   │   ├── config/             # CORS + WebSocket / STOMP broker config
│   │   ├── controller/
│   │   │   ├── AuthController.java    # POST /api/auth/register, /login
│   │   │   └── RoomController.java    # CRUD /api/rooms
│   │   ├── dto/                # Request / Response DTOs
│   │   ├── exception/          # GlobalExceptionHandler
│   │   ├── model/              # JPA entities: User, Room
│   │   ├── repository/         # Spring Data repos: UserRepository, RoomRepository
│   │   ├── security/           # JwtService, JwtFilter, SecurityConfig
│   │   ├── service/            # (future services)
│   │   └── websocket/
│   │       ├── RoomWebSocketController.java  # STOMP message handlers
│   │       ├── WsChannelInterceptor.java      # JWT auth on WS connect
│   │       └── WsMessages.java               # Payload record types
│   └── src/main/resources/
│       └── application.properties
│
├── CodeLink-frontend/
│   └── src/
│       ├── components/
│       │   ├── EditorHeader.jsx   # Toolbar: language, viewers, theme toggle
│       │   ├── IconRail.jsx       # Sidebar icon navigation
│       │   └── ui/                # Reusable UI primitives
│       ├── contexts/
│       │   └── AuthContext.jsx    # JWT token storage + getToken() helper
│       ├── pages/
│       │   ├── LoginPage.jsx      # Login form
│       │   ├── RegisterPage.jsx   # Registration form
│       │   ├── CreateRoomPage.jsx # Room creation with optional password
│       │   └── EditorPage.jsx     # Live editor + viewer count
│       └── services/
│           ├── authApi.js         # register / login HTTP calls
│           ├── roomApi.js         # createRoom / joinRoom / getRoom / deleteRoom
│           └── socketService.js   # STOMP connect / publish / disconnect
│
├── docs/
│   └── load-test-results.png  # Load testing dashboard screenshot
└── vercel.json                # Frontend deployment config

Getting Started

Prerequisites

Tool Version
Java 21+
Maven 3.9+ (or use included mvnw)
Node.js 18+
PostgreSQL 14+
Redis Any (Upstash cloud or local)

1 — Database Setup

-- Run as a PostgreSQL superuser:
CREATE DATABASE codelink;

Hibernate auto-creates all tables on first boot (ddl-auto: update).

2 — Backend

# Copy and fill in your secrets
cp CodeLink-backend/.env.example CodeLink-backend/.env
# CodeLink-backend/.env
SERVER_PORT=8081
REDIS_HOST=valued-mammal-99002.upstash.io
REDIS_PORT=6379
REDIS_PASSWORD=your_redis_password
REDIS_SSL_ENABLED=true
CORS_ALLOWED_ORIGINS=http://localhost:5173
ROOM_EXPIRY_HOURS=24
JWT_SECRET=your_base64_encoded_256bit_secret
# Windows
cd CodeLink-backend && mvnw.cmd spring-boot:run

# macOS / Linux
cd CodeLink-backend && ./mvnw spring-boot:run

Backend starts on http://localhost:8081.

3 — Frontend

cd CodeLink-frontend
cp .env.example .env      # pre-filled for local dev
npm install
npm run dev

Frontend starts on http://localhost:5173.

# CodeLink-frontend/.env
VITE_API_BASE_URL=http://localhost:8081

4 — Docker (Backend only)

cd CodeLink-backend
docker build -t codelink-backend .
docker run -p 8081:8081 --env-file .env codelink-backend

REST API Endpoints

Base URL: http://localhost:8081/api

All protected routes require the header:

Authorization: Bearer <jwt_token>

Authentication — /api/auth

Method Path Auth Request Body Response Description
POST /api/auth/register Public { "username": "alice", "password": "secret123" } { "token": "...", "username": "alice" } Register a new user; returns a JWT
POST /api/auth/login Public { "username": "alice", "password": "secret123" } { "token": "...", "username": "alice" } Authenticate; returns a JWT

Rooms — /api/rooms

Method Path Auth Request Body Response Description
POST /api/rooms Required { "password": "mypass" } RoomResponse Create a new room; password is optional
POST /api/rooms/join Public { "password": "mypass" } RoomResponse Join an existing room by its shared password
GET /api/rooms/{roomId} Public RoomResponse Fetch room metadata by ID
DELETE /api/rooms/{roomId} Required (owner) 204 No Content Delete a room; only the owner may delete

RoomResponse shape:

{
  "roomId": "a3f9c1d820",
  "owner": "alice",
  "createdAt": "2026-08-04T15:25:10Z"
}

Performance-Optimised Join Flow

The POST /api/rooms/join endpoint uses a two-layer lookup strategy to avoid slow BCrypt verification on every join:

Client sends password
        │
        ▼
  SHA-256(password) → Redis hash lookup (< 1 ms)
        │
   Hit? ──Yes──► Return room from cache (no BCrypt)
        │
        No
        │
        ▼
  DB lookup by SHA column (O(1) index scan)
        │
   BCrypt.matches() verify
        │
        ▼
  Re-populate Redis cache → Return RoomResponse

WebSocket Events

Endpoint: ws://localhost:8081/ws (SockJS fallback enabled)

Connect with STOMP headers:

Authorization: Bearer <jwt_token>
roomId: <roomId>

Subscribe to receive all room events:

/topic/room/{roomId}

Client → Server (Publish destinations)

Destination Body Description
/app/room/{roomId}/sync {} Request current code state on first load
/app/room/{roomId}/edit { "code": "...", "senderSession": "abc123" } Broadcast a code edit to all subscribers

Server → Client (Broadcast topics)

All messages arrive on /topic/room/{roomId}.

CodeBroadcast — sent on sync and every edit:

{
  "type": "CODE",
  "code": "console.log('hello')",
  "viewers": 3,
  "senderSession": "abc123"
}

ViewerCount — sent on every connect / disconnect:

{
  "type": "VIEWERS",
  "viewers": 2
}

senderSession lets the originating tab suppress the echo — only other tabs apply the incoming edit.


Application Workflow

┌─────────────────────────────────────────────────────────────┐
│                         BROWSER (Client)                    │
│                                                             │
│  1. Register / Login ──HTTP POST /api/auth──► JWT Token     │
│                                                             │
│  2. Create Room ──HTTP POST /api/rooms──► { roomId }        │
│     OR                                                      │
│     Join Room  ──HTTP POST /api/rooms/join──► { roomId }    │
│                                                             │
│  3. Open EditorPage                                         │
│     ├─ Connect STOMP (SockJS) with JWT + roomId header      │
│     ├─ Server increments viewer count → broadcast VIEWERS   │
│     ├─ Publish SYNC → receive current code from Redis/DB    │
│     └─ Monaco Editor ready                                  │
│                                                             │
│  4. Type in editor                                          │
│     └─ Publish EDIT { code, senderSession }                 │
│           │                                                 │
│           ▼                                                 │
│     Server writes to Redis (sync) + PostgreSQL (async)      │
│           │                                                 │
│           ▼                                                 │
│     Broadcast CODE to all /topic/room/{roomId} subscribers  │
│           │                                                 │
│     Other tabs receive → apply if senderSession ≠ self      │
│                                                             │
│  5. Close tab / navigate away                               │
│     └─ STOMP disconnect → viewer count decremented          │
│                                                             │
└─────────────────────────────────────────────────────────────┘

┌──────────────┐      JWT Auth       ┌──────────────────────┐
│   Frontend   │◄───────────────────►│   Spring Boot API    │
│  React/Vite  │    REST + STOMP     │    :8081             │
└──────────────┘                     └──────┬───────────────┘
                                            │
                               ┌────────────┴────────────┐
                               │                         │
                        ┌──────▼──────┐         ┌───────▼──────┐
                        │  PostgreSQL  │         │    Redis      │
                        │ (durable     │         │ (live buffer  │
                        │  store)      │         │  + room cache)│
                        └─────────────┘         └──────────────┘

Step-by-step User Journey

1. Register/Login
   └── POST /api/auth/register or /login
       └── Receive JWT token → stored in AuthContext (sessionStorage)

2. Create a Room
   └── POST /api/rooms { password: "mypass" }
       ├── Room saved to PostgreSQL (owner = authenticated user)
       ├── SHA-256(password) stored for fast O(1) DB lookups
       ├── Room metadata cached in Redis Hash (TTL: 24h)
       └── Receive { roomId, owner, createdAt }

3. Share Room
   └── Share the password with collaborators (out-of-band)

4. Collaborator Joins
   └── POST /api/rooms/join { password: "mypass" }
       ├── SHA-256 checked in Redis → cache hit? Return immediately (< 1 ms)
       └── Cache miss? DB lookup by SHA → BCrypt verify → re-cache

5. Enter Editor (both users)
   └── STOMP connect to ws://localhost:8081/ws
       ├── WsChannelInterceptor validates JWT on connect
       ├── Server maps STOMP sessionId → roomId in-memory
       ├── Viewer count incremented → ViewerCount broadcast
       ├── Client publishes SYNC → server sends current code from Redis
       └── Monaco Editor displays synced code

6. Collaborative Editing
   └── User A types → EDIT published → Redis updated → CODE broadcast
       └── User B receives CODE → senderSession ≠ B's session → apply diff

7. Leave
   └── STOMP disconnect event → viewer count decremented → broadcast

Load Testing Results

Load test run against the deployed backend using 25 Virtual Users for 1 minute (Fixed profile).

CodeLink Load Test — 25 VUs, 1 min, 3,300 requests, 0% errors

Summary

Metric Value
Total Requests Sent 3,300
Requests / second 82.86
Avg. Response Time 262 ms
P90 433 ms
P95 738 ms
P99 1,473 ms
Error % 0.00% ✅
Failure % 0.00% ✅
Peak CPU % 40.9%
Peak Memory % 96.7%

Observations

  • Zero errors or failures across all 3,300 requests under 25 concurrent users.
  • Avg. response time of 262 ms is well within acceptable range for a WebSocket-heavy collaborative app.
  • The Redis caching layer (room-join bypass of BCrypt) is the primary reason P50/P90 stays low — only P99 (1,473 ms) shows occasional DB/BCrypt overhead on cache-cold joins.
  • Peak Memory at 96.7% indicates the process is memory-bound at this concurrency level; scaling horizontally or increasing heap is recommended for > 50 VUs.
  • CPU at 40.9% leaves comfortable headroom for additional load.

Environment Variables Reference

Backend (CodeLink-backend/.env)

Variable Default Description
SERVER_PORT 8081 HTTP port the app listens on
REDIS_HOST Redis hostname
REDIS_PORT 6379 Redis port
REDIS_PASSWORD Redis auth password
REDIS_SSL_ENABLED true Enable TLS for Redis connection
CORS_ALLOWED_ORIGINS http://localhost:5173 Comma-separated allowed origins
ROOM_EXPIRY_HOURS 24 Redis TTL for room code buffer
JWT_SECRET Base64-encoded 256-bit HMAC secret

Frontend (CodeLink-frontend/.env)

Variable Default Description
VITE_API_BASE_URL http://localhost:8081 Backend base URL

Deployment

The repository is pre-configured for Vercel (frontend) via vercel.json. The backend can be containerised using the provided Dockerfile.

# Build backend JAR
cd CodeLink-backend && mvnw.cmd package -DskipTests

# Build Docker image
docker build -t codelink-backend .

Set all environment variables in your cloud provider's dashboard before deploying.


License

MIT

About

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages