Case Study: Discol
A technical deep dive into the architecture and engineering decisions behind a full-stack real-time chat platform with voice rooms, role-based permissions, and production-grade deployment.
Problem & Goals
Modern chat platforms feel simple on the surface, but they rely on a dense mix of systems behind the scenes: identity, real-time communication, permissions, presence, media, and deployment reliability. Most portfolio projects stop at the UI layer. The challenge was to go deeper.
The goal was to design and ship a complete real-time product — a system where users can register, connect with friends, join communities, chat instantly, manage roles and permissions, enter voice rooms, and interact with a guided demo experience. Just as importantly, the project had to reflect production-minded engineering practices: automated testing, containerized environments, and continuous delivery.
What Was Built
Discol is a full-stack real-time chat application built around servers, channels, friendships, and role-based collaboration. Users can sign up with email and password or Google OAuth, create servers, invite other users, organize conversations into text and voice channels, and manage server membership through roles and permissions.
Real-time Messaging
Text messages delivered instantly via Socket.IO. Presence updates are pushed only to relevant users rather than broadcast globally.
Voice Rooms
Channel-based audio and video sessions powered by LiveKit, accessible directly from within the application.
Role-based Permissions
Explicit server permissions for channel creation, invite management, role management, and server deletion modeled in the database.
Media Uploads
Profile avatar uploads through Cloudinary with cloud storage and serving.
Demo Mode
Seeded content and a guided product tour so recruiters and reviewers can explore the product without populating it manually.
Localization
English and Spanish language support built into the frontend interface.
UI/UX Design Decisions
The interface was designed around familiar messaging patterns to minimize the learning curve for users. The layout follows a three-column structure consisting of server navigation, channel list, and active chat view.
A dark theme was chosen to improve readability during long messaging sessions and match the design conventions of modern collaboration tools.

System Architecture
The frontend is a Next.js 16 application using the App Router, React 19, and Tailwind CSS v4. It handles page routing, responsive UI, long-lived client state, and real-time event subscriptions. Shared contexts keep the interface synchronized as events arrive.
The backend is a NestJS 11 modular monolith. Focused modules cover authentication, users, friendships, servers, roles, channels, messages, voice access, and real-time gateway events. All REST endpoints are exposed under /api, while Socket.IO runs alongside for low-latency messaging.
For data persistence, the app uses PostgreSQL with Prisma ORM. The schema models users, OAuth accounts, friendships, servers, invites, members, roles, channels, and messages. Role-based access control is enforced through explicit server permissions, keeping authorization rules visible and maintainable.
The real-time layer uses two communication patterns: channel rooms for chat fanout (only users in the active channel receive live messages) and user-specific rooms for private notifications like friend requests, server invites, and presence changes.

Authentication Flow
- 1. User authenticates via REST API endpoint
- 2. Server generates and returns a JWT token
- 3. Client attaches the token during WebSocket handshake
- 4. WebSocket gateway validates authentication
- 5. Authenticated users join channel rooms
Message Flow
- Client sends message through WebSocket gateway
- Gateway validates user authentication
- Message stored in PostgreSQL
- Server broadcasts message to channel room
- Connected clients receive update instantly

Technology Decisions
Next.js 16, React 19, Tailwind CSS v4
App Router with server components, responsive layouts, long-lived client state, and real-time event subscriptions.
NestJS 11, Prisma ORM, PostgreSQL
Modular monolith with focused domain modules. Prisma models the full collaboration schema with explicit RBAC permissions.
Socket.IO
Persistent bidirectional connections for instant message delivery and targeted user notifications.
LiveKit
Channel-based audio and video sessions accessible directly from within the application.
JWT, Passport, Google OAuth 2.0
Supports email/password registration and Google OAuth. JWTs are validated across both REST and WebSocket layers.
Cloudinary
Profile avatar uploads with cloud storage, allowing users to personalize their accounts.
Jest, Supertest, Playwright
Layered test strategy: frontend unit tests, backend unit and integration tests, and browser-level end-to-end specs.
Docker, Docker Compose, GitHub Actions, GHCR, VPS/SSH
Multi-stage container builds, automated CI pipelines, and production deployment over SSH to a Linux VPS behind Nginx.
Continuous Integration & Deployment
- 1. Code push triggers GitHub Actions workflow
- 2. Unit and end-to-end tests run automatically
- 3. Docker image is built
- 4. Container deployed to Linux VPS
- 5. Nginx routes traffic to the application
Challenges & Lessons Learned
Making real-time features feel selective instead of noisy
Messaging, status updates, channel changes, invites, and friendship actions all have different audiences. The solution was to separate channel-level events from user-level events — messages are emitted only to the relevant channel room, while friend and invite notifications are emitted directly to the affected users. This kept the real-time layer clean and closer to how a production chat system should behave.
Keeping authorization understandable as features expanded
As soon as server management was added, authorization became more complex than simple ownership checks. Channel creation, role management, inviting users, renaming a server, and removing members all needed explicit rules. Instead of scattering those checks across controllers and services, a dedicated permission system was modeled in Prisma and enforced through NestJS guards — giving the project a clear RBAC story that is easy to explain and extend.
Verifying a real-time product across multiple layers
A real-time app can appear to work while hiding fragile behavior across API flows, sockets, and UI state. To reduce that risk, a layered test strategy was built: backend unit tests for service and module logic, backend integration tests against a real PostgreSQL database, frontend unit tests for components and client utilities, and Playwright end-to-end tests that boot the full stack in Docker. That approach gave the project much stronger credibility than a visual demo alone.
Outcomes
Discol became a strong end-to-end case study in building and shipping a modern real-time product. It demonstrates full-stack ownership across product design, domain modeling, authentication, authorization, WebSocket workflows, voice integration, media uploads, internationalization, testing, and deployment.
All tests run automatically in GitHub Actions pipelines that gate pull requests before deployment — making the project not just a feature demo, but a credible example of engineering quality and delivery from start to finish.