A scalable real-time communication platform inspired by Discord, featuring server creation, channel-based messaging, voice/video calling, and real-time collaboration.
Get Started •
Features •
API •
Deploy •
Contribute
Overview
Features
Tech Stack
Quick Start
Project Structure
Environment Variables
API Documentation
Frontend Routes
Real-time Features
Database Schema
Deployment
Security
Troubleshooting
FAQ
Contributing
License
LinkSphere is a full-stack real-time communication platform that enables users to create servers, join communities, and communicate through channel-based messaging. Inspired by Discord, it provides a modern platform for teams and communities to collaborate in real-time.
Server Management — Create, customize, and manage private servers
Channel System — Organized text and voice channels within servers
Real-time Messaging — Instant message delivery with Socket.io
Voice/Video Calls — WebRTC-powered peer-to-peer calling
User Presence — Real-time online/offline status tracking
Rich Media — File and image attachments via Cloudinary
Authentication & Security
Feature
Description
JWT Authentication
Secure token-based auth with automatic expiry handling
Password Hashing
bcrypt for secure password storage
Google OAuth
Social login via Passport.js with Google Strategy
Protected Routes
Server-side JWT verification for all protected endpoints
Input Validation
Zod schemas for request validation
Feature
Description
Create Servers
Create custom servers with name and icon
Edit Servers
Update name, icon, and color theme
Delete Servers
Owners can delete their servers
Invite System
Generate unique invite codes
Member Management
View and manage server members
Channel System
Create and manage text channels
Feature
Description
Real-time Messaging
Instant delivery via Socket.io
Channel-based Chat
Organized conversations in channels
Message History
Persistent storage in MongoDB
System Messages
Automated messages for events
File Attachments
Image and file uploads
Feature
Description
WebRTC Calling
Peer-to-peer voice and video calls
Call Signaling
Socket.io-based call management
Audio Toggle
Enable/disable microphone
Video Toggle
Enable/disable camera
Call Modal
UI for initiating calls
Feature
Description
Modern UI
Discord-inspired dark theme
Responsive Design
Desktop and mobile support
User Settings
Profile customization
Avatar Uploads
Custom profile pictures
Online Presence
Real-time status indicators
Technology
Version
Purpose
React
19
UI Framework
React Router
7
Client-side routing
Vite
6
Build tool
Tailwind CSS
4
Styling
Technology
Version
Purpose
Node.js
25
Runtime
Express
5
Web framework
Socket.io
4
Real-time communication
mongoose
9
MongoDB ODM
Technology
Purpose
MongoDB
Primary database
Cloudinary
Image/file storage
Technology
Purpose
JWT
Token-based auth
bcrypt
Password hashing
Passport.js
OAuth strategy
Google OAuth 2.0
Social login
Technology
Purpose
Zod
Input validation
WebRTC
Voice/video calls
concurrently
Dev orchestration
nodemon
Development watcher
Ensure you have the following installed:
Node.js — Version 18 or higher
MongoDB — Local installation or MongoDB Atlas cloud
npm — Comes with Node.js
Service
Required
Purpose
Cloudinary
No
Image/file uploads
Google OAuth
No
Social login
# Clone the repository
git clone https://github.com/anomalyco/linksphere.git
cd linksphere
# Install root dependencies
npm install
# Install backend dependencies
npm install --prefix server
# Install frontend dependencies
npm install --prefix client
Create a .env file in the project root:
# Server Configuration
PORT = 8000
NODE_ENV = development
# Database Connection
MONGODB_URI = mongodb://localhost:27017/linksphere
# JWT Secrets (generate strong random strings)
JWT_SECRET = your_super_secret_key_at_least_32_characters_long
SESSION_SECRET = your_session_secret_key
# Cloudinary (optional - for file uploads)
CLOUDINARY_CLOUD_NAME = your_cloud_name
CLOUDINARY_API_KEY = your_api_key
CLOUDINARY_API_SECRET = your_api_secret
# Google OAuth (optional - for social login)
GOOGLE_CLIENT_ID = your_google_client_id
GOOGLE_CLIENT_SECRET = your_google_client_secret
# Application URL
CLIENT_URL = http://localhost:5173
# Generate a secure JWT secret
openssl rand -base64 32
# Generate a session secret
openssl rand -base64 32
Development Mode (Recommended)
# Starts both backend and frontend concurrently
npm run dev
This will start:
# Backend only
npm run server
# Frontend only
npm run client
# Build the client for production
npm run build
# Start production server
npm start
linksphere/
│
├── server/ # Express Backend
│ ├── server.js # Entry point (HTTP + Socket.io)
│ ├── app.js # Express app configuration
│ │
│ ├── config/
│ │ └── passport.js # Passport Google OAuth strategy
│ │
│ ├── controllers/ # Business logic
│ │ ├── userController.js
│ │ ├── serverController.js
│ │ ├── dmController.js
│ │ └── friendController.js
│ │
│ ├── database/
│ │ └── db.js # MongoDB connection
│ │
│ ├── middleware/
│ │ ├── authMiddleware.js # JWT verification
│ │ ├── validate.js # Zod validation
│ │ └── errorMiddleware.js # Error handling
│ │
│ ├── models/ # Mongoose models
│ │ ├── User.js
│ │ ├── Server.js
│ │ ├── Message.js
│ │ ├── Attachment.js
│ │ └── DirectMessage.js
│ │
│ ├── routes/ # API routes
│ │ ├── authRoutes.js
│ │ ├── userRoutes.js
│ │ ├── serverRoutes.js
│ │ ├── dmRoutes.js
│ │ ├── friendRoutes.js
│ │ └── uploadRoutes.js
│ │
│ ├── validations/ # Zod schemas
│ │ ├── userSchemas.js
│ │ └── serverSchemas.js
│ │
│ ├── utils/ # Helper functions
│ │ ├── ApiError.js
│ │ ├── cloudinaryHelper.js
│ │ └── catchAsync.js
│ │
│ ├── scripts/ # Utility scripts
│ │ └── fixGoogleIdIndex.js
│ │
│ ├── package.json
│ └── server/
│
├── client/ # React Frontend
│ ├── src/
│ │ ├── App.jsx # Router configuration
│ │ ├── main.jsx # Entry point
│ │ └── index.css # Global styles
│ │
│ ├── context/
│ │ └── AuthContext.jsx # Auth state management
│ │
│ ├── components/
│ │ ├── Navbar.jsx
│ │ ├── Logo.jsx
│ │ ├── AuthForm.jsx
│ │ ├── ProtectedRoute.jsx
│ │ ├── CreateServerModal.jsx
│ │ ├── EditServerModal.jsx
│ │ ├── JoinServerModal.jsx
│ │ ├── CallModal.jsx
│ │ └── ...
│ │
│ ├── pages/
│ │ ├── Landing.jsx
│ │ ├── Login.jsx
│ │ ├── Signup.jsx
│ │ ├── AppPage.jsx
│ │ ├── AppPage.module.css
│ │ ├── UserSettings.jsx
│ │ ├── UserSettings.module.css
│ │ └── OAuthCallback.jsx
│ │
│ ├── hooks/
│ │ └── useVoiceChannel.jsx
│ │
│ ├── utils/
│ │ └── theme.js
│ │
│ ├── public/
│ │ └── favicon.svg
│ │
│ ├── index.html
│ ├── package.json
│ ├── vite.config.js
│ └── eslint.config.js
│
├── package.json # Root package.json
├── nodemon.json # Nodemon configuration
└── README.md # This file
Variable
Required
Default
Description
PORT
Yes
8000
Server port number
NODE_ENV
No
development
Environment mode
MONGODB_URI
Yes
-
MongoDB connection string
JWT_SECRET
Yes
-
JWT signing secret
SESSION_SECRET
Yes
-
Session secret
CLOUDINARY_CLOUD_NAME
No
-
Cloudinary cloud name
CLOUDINARY_API_KEY
No
-
Cloudinary API key
CLOUDINARY_API_SECRET
No
-
Cloudinary API secret
GOOGLE_CLIENT_ID
No
-
Google OAuth client ID
GOOGLE_CLIENT_SECRET
No
-
Google OAuth client secret
CLIENT_URL
No
http://localhost:5173
Frontend URL
Base URL: http://localhost:8000/api
POST /auth/signup
Content-Type: application/json
Request:
{
"username": "string",
"email": "user@example.com",
"password": "string",
"dob": "YYYY-MM-DD"
}
Response (201):
{
"success": true,
"data": {
"id": 1234567890,
"username": "string",
"email": "user@example.com",
"avatarUrl": "string"
},
"token": "jwt_token"
}
POST /auth/login
Content-Type: application/json
Request:
{
"email": "user@example.com",
"password": "string"
}
Response (200):
{
"success": true,
"data": {
"id": 1234567890,
"username": "string",
"email": "user@example.com",
"avatarUrl": "string"
},
"token": "jwt_token"
}
Redirects to Google for authentication.
GET /auth/google/callback
Handles OAuth callback, returns JWT token.
Method
Endpoint
Auth
Description
GET
/users/:id
Yes
Get user by ID
PUT
/users/:id
Yes
Update user
DELETE
/users/:id
Yes
Delete user
POST /servers
Authorization: Bearer <token>
Content-Type: application/json
Request:
{
"name": "Server Name",
"iconUrl": "url (optional)",
"color": "#hex (optional)"
}
Response (201):
{
"success": true,
"data": {
"id": 1234567890,
"name": "Server Name",
"inviteCode": "abc123",
...
}
}
Method
Endpoint
Auth
Description
POST
/servers
Yes
Create server
GET
/servers/mine
Yes
Get user's servers
GET
/servers/:id
Yes
Get server
PATCH
/servers/:id
Yes
Update server
DELETE
/servers/:id
Yes
Delete server
Method
Endpoint
Auth
Description
POST
/servers/:id/channels
Yes
Create channel
DELETE
/servers/:id/channels/:channelId
Yes
Delete channel
Method
Endpoint
Auth
Description
GET
/servers/:id/channels/:channelId/messages
Yes
Get messages
POST
/servers/:id/channels/:channelId/messages
Yes
Send message
Method
Endpoint
Auth
Description
GET
/servers/invite/:code
Yes
Get server by code
POST
/servers/invite/:code/join
Yes
Join via invite
Method
Endpoint
Auth
Description
POST
/servers/:id/join
Yes
Join server
POST
/servers/:id/leave
Yes
Leave server
POST /upload
Authorization: Bearer <token>
Content-Type: multipart/form-data
Request:
{
"file": File
}
Response (201):
{
"success": true,
"data": {
"url": "https://...",
"type": "image"
}
}
Method
Endpoint
Auth
Description
GET
/health
No
Health check
Route
Description
Auth
/
Landing page
No
/login
Login page
No
/signup
Signup page
No
/app
Main application
Yes
/app/settings
User settings
Yes
/oauth-callback
OAuth callback
No
/invite/:code
Join via invite
Yes
Event
Direction
Description
connect
Server
Client connects
disconnect
Server
Client disconnects
Event
Payload
Description
join_channel
{ channelId }
Join channel room
leave_channel
{ channelId }
Leave channel room
message
{ message }
Broadcast new message
Event
Payload
Description
online-users-list
{ users[] }
Initial online users
user-online
{ userId }
User came online
user-offline
{ userId }
User went offline
Event
Payload
Description
call-user
{ to, from, callType }
Initiate call
call-incoming
{ from, callType }
Incoming call
call-accepted
{ to, sdp }
Call accepted
call-rejected
{ to }
Call rejected
call-ended
{ from }
Call ended
ice-candidate
{ to, candidate }
ICE candidate
user-left-call
{ userId }
User left call
offer
{ to, sdp }
WebRTC offer
answer
{ to, sdp }
WebRTC answer
The application uses WebRTC for peer-to-peer voice and video calls:
Call Initiation — User A initiates call via Socket.io
Signaling — Socket.io exchanges SDP offers/answers
ICE Candidates — ICE candidates exchanged via socket
Direct Connection — Peers connect directly via WebRTC
{
id : Number , // Unique ID (timestamp-based)
username : String , // Display name (unique)
email : String , // Email (unique)
password : String , // Hashed password
dob : Date , // Date of birth
googleId : String , // Google OAuth ID
avatarUrl : String , // Profile picture URL
socketId : String , // Current socket connection
createdAt : Date ,
updatedAt : Date
}
{
id : Number , // Unique ID
name : String , // Server name
iconUrl : String , // Server icon URL
inviteCode : String , // Unique invite code
ownerId : Number , // Owner user ID
members : Number [ ] , // Member user IDs
color : String , // Theme color (hex)
channels : [ {
id : String ,
name : String ,
type : String // "text" or "voice"
} ] ,
createdAt : Date ,
updatedAt : Date
}
{
id : Number , // Unique ID
serverId : Number , // Server ID
channelId : String , // Channel ID
authorId : Number , // Author user ID
authorName : String , // Author username
content : String , // Message content
attachmentUrl : String , // Attachment URL
type : String , // "user" or "system"
timestamp : Date ,
createdAt : Date ,
updatedAt : Date
}
# Install and build client
npm run build
This creates an optimized build in client/dist/.
Set NODE_ENV=production
Configure production MongoDB URI
Set secure JWT and session secrets
Configure Cloudinary for production
Set CLIENT_URL to your production URL
# Use Node.js base image
FROM node:18-alpine
# Set working directory
WORKDIR /app
# Copy package files
COPY package*.json ./
# Install dependencies
RUN npm install
# Copy source code
COPY . .
# Build client
RUN npm run build
# Expose port
EXPOSE 8000
# Start server
CMD ["npm" , "start" ]
Implemented Security Measures
Password Hashing — All passwords hashed with bcrypt
JWT Tokens — Secure token-based authentication
Input Validation — Zod schemas validate all input
Protected Routes — Middleware verifies JWT on protected routes
CORS — Configured for allowed origins
Rate Limiting — Express rate limiting (optional)
Use strong, unique secrets for JWT_SECRET and SESSION_SECRET
Enable HTTPS in production
Keep Node.js and dependencies up to date
Use environment variables for secrets
Implement rate limiting for API endpoints
MongoDB Connection Failed
# Check MongoDB is running
mongod
# Verify connection string in .env
MONGODB_URI=mongodb://localhost:27017/linksphere
# Find process using port
lsof -i :8000
# Kill process
kill -9 < PID>
# Or use different port
PORT=8001
JWT tokens expire after 7 days. Re-login to get a new token.
Socket.io Connection Issues
Ensure client connects to correct port
Check firewall allows WebSocket connections
Verify NODE_ENV is not blocking connections
Verify Cloudinary credentials in .env
Check API keys have upload permissions
Verify file size is under limit
Q: How do I create a server?
A: Click the "+" icon next to "Servers" in the sidebar, then select "Create Server".
Q: How do I invite friends?
A: Open server settings, click "Invite", and share the generated invite code.
Q: How do I join a server?
A: Use an invite link like http://localhost:5173/invite/abc123 or enter the code in "Join Server".
Q: How do voice calls work?
A: Click the phone icon on a user's profile to initiate a voice call. Both parties need to accept.
A: Yes, drag and drop images into the message input or use the attachment button.
Q: How do I change my avatar?
A: Go to Settings > Profile and upload a new avatar image.
Contributions are welcome! Please follow these steps:
Fork the repository
Create a feature branch (git checkout -b feature/amazing-feature)
Commit your changes (git commit -m 'Add amazing feature')
Push to the branch (git push origin feature/amazing-feature)
Open a Pull Request
Use meaningful variable and function names
Add comments for complex logic
Follow existing code style
Run lint before submitting
ISC License — See LICENSE for details.
Built with passion using the MERN stack