Skip to content
DAdhruuv.me / projects
← All projects

02 / 06 Backend Developer live demo below

Real-Time Collaboration Platform

Developed a robust meeting system using Spring Boot, WebSockets, and WebRTC, enabling real-time chat, audio, and video communication with minimal latency. Architected core microservices like Config Server, Eureka Discovery Server, Gateway Server, and Meet Signalling Server, ensuring scalable, low-latency, and high-performance communication.

  1. 01 · The constraint

    Real-time meetings break monoliths

    A monolith looks fine for the first sprint — auth, chat, presence, and a stubbed audio relay all in one Spring Boot app. The trouble is that 'real-time' has a latency budget the monolith can't share. A slow product-catalog query on the same JVM shows up as a 200 ms hitch in someone's hand-raise.

    The deeper issue is operational. Every feature touches the same WAR. Every deploy is a meeting interruption. Scaling the audio relay separately from the user-profile service is impossible while they share a thread pool.

  2. 02 · The split

    Seven services, one capability each

    The platform is seven small Spring Boot apps. Frontend, Gateway, User Service, Product Service, MeetingSignaling, Config Server, Eureka Discovery. Each one boots in seconds, can be rebuilt independently, and uses Eureka to find its neighbours.

    The hard part isn't writing seven apps — it's making them coordinate. The next three chapters are the supporting cast: how they discover each other, how external traffic enters, and how the signaling fan-out actually works.

  3. 03 · Cross-cutting plumbing

    Eureka holds the address book. Config Server holds the secrets.

    Every service boots, reads optional:configserver:http://localhost:8888 from its application.yml, and pulls its environment from the Config Server. No hard-coded hostnames, no per-environment WARs.

    After config, each service registers with Eureka at :8761. From that point the Gateway can route to signaling by name instead of by host:port, and replacing the signaling service's machine is just a restart away from the rest of the cluster knowing about it.

    ●MeetingSignalingService/src/main/resources/application.ymlyaml
    1spring:
    2 application:
    3 name: signaling
    4 config:
    5 import: optional:configserver:http://localhost:8888
    6eureka:
    7 client:
    8 service-url:
    9 defaultZone: http://localhost:8761/eureka
    10server:
    11 port: 8091
  4. 04 · The edge

    Spring Cloud Gateway · the only public port

    Everything outside the cluster talks to one place: :8222, the Gateway. The Gateway uses Spring Cloud's predicate-route DSL — match a path prefix, forward to a service. No service besides the Gateway has its port exposed.

    This also makes auth one-shot. The Gateway runs the token check; the downstream services trust the gateway to have done so and read the user out of forwarded headers.

    ●Gateway/src/main/resources/application.ymlyaml
    1cloud:
    2 gateway:
    3 routes:
    4 - id: user
    5 uri: http://localhost:8090
    6 predicates:
    7 - Path=/api/v1/users/**
    8 - id: frontend
    9 uri: http://localhost:8092
    10 predicates:
    11 - Path=/meet/**
  5. 05 · The hot loop

    STOMP over WebSocket · six signal types, one topic per room

    The signaling service is where every meeting lives. Each browser opens a STOMP subscription to /topic/room/{roomId} and publishes to /app/sendSignal. The controller dispatches by SignalType — JOIN, LEAVE, CHAT, MIC, VIDEO, HAND_RAISE — and re-emits onto the topic so every other peer in the room gets the update.

    The fan-out is the broker's job, not Spring's. The service stays stateless: any instance can serve any room, the broker handles the rest.

    ●MeetingSignalingService/.../SignalingController.javajava
    1@MessageMapping("/sendSignal")
    2@SendTo("/topic/room/roomId")
    3public SignalingMessage handle(SignalingMessage m,
    4 SimpMessageHeaderAccessor h) {
    5 switch (m.getSignalType()) {
    6 case JOIN -> signalingService.addUserToRoom(m, h);
    7 case LEAVE -> signalingService.removeUserFromRoom(m, h);
    8 case CHAT -> signalingService.broadcastChatMessage(m, h);
    9 case MIC -> signalingService.broadcastMicStatusUpdate(m, h);
    10 case VIDEO -> signalingService.broadcastVideoStatusUpdate(m, h);
    11 case HAND_RAISE -> signalingService.broadcastHandRaise(m, h);
    12 }
    13 return m;
    14}
  6. 06 · Why this scales

    Media bytes never touch our servers

    Audio and video go peer-to-peer via WebRTC. The signaling service helps two browsers find each other — JOIN tells the other peers about a newcomer, the signaling channel carries the SDP offer/answer plus ICE candidates — and then steps out of the way.

    Server-side cost per meeting is just the WebSocket fan-out for the six signal types above. The bandwidth-heavy part of a call (video) is bilateral between peers. Doubling meeting count doubles socket count, not bandwidth.

  7. 07 · Try it

    Open a room. Share the link. Anyone joins.

    The widget below is a real RTCPeerConnection negotiated in your browser. The signaling backend is /api/webrtc/signal — Upstash Redis if configured, in-memory otherwise — running the same hello → offer → answer → ICE dance the production service runs over STOMP. Open the share URL on another device and the two browsers pair directly; media never touches our server.

stack

Built with.

Spring BootWebSocketsWebRTCMicroservicesGateway ServerEureka Discovery
Want the story behind it?I answer email

Senior backend and platform roles, hard systems problems, or an argument about thread pools.

Open a connectionRecruiter mode