Back to Projects

⚽ Futbol Chat FULLSTACK

NestjsGraphQLApolloTypeScriptWebSocketsPrismaDockerPostgreSQLRedis

Real-time Chat APP with React, Nestjs GraphQL, WebSockets & Advanced Caching

⚽ Futbol Chat FULLSTACK

Links

View on GitHub

Created

October 21, 2025

Project README

Detailed documentation about this project

Technologies

NestJSGraphQLWebSocketsDockerPostgreSQLRedisPrismaClass ValidatorJWT AuthenticationWinstonReactTypeScriptApollo ClientCodegenTailwind CSSShadcn UIReact-Hook-FormZodReact Compiler

Overview

A real-time fullstack chat application supporting public and private rooms, user profiles, and advanced message caching. Built with NestJS GraphQL for the backend and React for the frontend, featuring WebSocket subscriptions for instant messaging and Redis for message caching.

What & Why

What

A real-time chat app for Argentine soccer fans. Public and private chat rooms where you can talk about teams, players, or whatever you want with other fans.

Why

I've been working with NestJS for a while now, so I felt comfortable using it for the backend. Same with GraphQL—I really enjoy working with it and it fits perfectly for real-time features like subscriptions. Since I love soccer, it only made sense to build something around that. My projects always tend to have some kind of connection to soccer, it's just what I like.

The Challenge

Building a chat that's fast, smooth, and handles many users connected at the same time. Also managing real-time user presence and making sure everything scales well.

Highlights

  • Layered Architecture
  • SOLID Principles + Design Patterns
  • Cache-Aside Pattern with Redis (50-100ms faster)
  • Cursor-Based Pagination
  • JWT Authentication + bcrypt hashing
  • HTTP-only Secure Cookies
  • Distributed Rate Limiting (Redis-backed)
  • GraphQL + WebSockets (real-time features)
  • Live User Presence tracking with Redis Sets
  • Typing Indicators with efficient broadcasting
  • Pub/Sub Architecture for scalable event distribution
  • Structured Logging (Winston for Dev and Prod environments)
  • Correlation IDs for Request Tracing
  • Performance Metrics
  • Custom Error Formatting
  • Redis: Pub/Sub for real-time updates, Rate-limiting using Redis tokens, Caching frequently fetched data

Features

  • Real-time messaging with WebSocket subscriptions (GraphQL)
  • Create and manage public and private chat rooms
  • User authentication and profile management
  • Cache-Aside Pattern with Redis
  • Cursor-Based Pagination for messages
  • Redis Pub/Sub for real-time events - Typing indicators - Live User Precense
  • Room administration - invite users, edit settings, delete rooms
  • User search and friend-like system for private rooms
  • Message persistence with PostgreSQL
  • JWT Authentication
  • Rate Limiting (Redis backed)
  • Structured Logging with Winston (Dev and Prod environments)
  • Correlation IDs for Request Tracing
  • Performance Metrics
  • Error Handler
  • For the Frontend - React Compiler (automatically applies memoization)
  • User registration with validation
  • Secure login with JWT tokens
  • Profile updates (including avatar upload)
  • User search functionality
  • Session management with refresh tokens
  • Add/remove members with access control
  • Chatroom metadata (name, description, color, image)
  • List chatrooms per user with caching
  • Delete chatrooms with cascade cleanup
  • Send text messages with validation
  • Image upload via GraphQL multipart
  • Paginated message history (cursor-based)
  • Real-time message delivery via subscriptions
  • Message timestamps with timezone support
  • Live User Presence - Track active users per chatroom
  • User Events - Join/leave notifications
  • Connection Management - WebSocket lifecycle hooks

Architecture

Type: Layered Architecture with NestJS

Redis is the backbone of the system: Pub/Sub for real-time updates, Rate-limiting using Redis tokens, Caching frequently fetched data. Because Redis operates in memory, the performance gain was huge.

Principles

  • SOLID Principles
  • Design Patterns
  • Cache-Aside Pattern
  • Event-Driven Architecture

Layers

modules/

Feature modules (auth, user, chatroom, live-chatroom, PubSub)

common/

Shared utilities (cache, constants, guards, interceptors, middleware, plugins, throttler, logger)

Performance Optimizations

OptimizationImplementation
Cache-Aside PatternRedis caching with TTL for frequently accessed data - 50-100ms faster
Cursor-Based PaginationAPI returns limited items with cursor pointing to next batch - reduces payload size
Redis Pub/SubReal-time event distribution across multiple instances
Rate Limiting@nestjs/throttler with Redis storage - different limits per operation type
Structured LoggingWinston with JSON in production, human-readable in dev
Correlation IDsRequest tracing across all services

Security

FeatureImplementation
JWT AuthenticationStateless authentication tokens with @nestjs/jwt
Password Hashingbcrypt for secure password storage
Input Validationclass-validator decorators
Custom GuardsFile-grained authorization
HTTP-only CookiesSecure cookie storage for tokens
Rate LimitingRedis-backed distributed rate limiting

Project Structure

Type: NestJS Module-based

src/modules/auth/- Authentication & JWT
src/modules/user/- User management
src/modules/chatroom/- Chatroom operations
src/modules/live-chatroom/- Real-time tracking
src/modules/PubSub/- Redis Pub/Sub setup
src/common/cache/- Redis caching service
src/common/constants/- Constants for Inject dependencies
src/common/filter/- GraphQL exception filter
src/common/guards/- Custom Guards
src/common/interceptors/- Request interceptors
src/common/middleware/- Custom middleware
src/common/plugins/- Logger Plugin to catch graphql context
src/common/throttler/- GraphQLThrottlerGuard
src/common/token/- TokenService - verify refresh token
src/common/types/- GraphQLExecutionTypes
src/common/interfaces/- GraphQLContext, ILogger, etc
src/common/logger/- Logging configuration
src/common/utils/- Helper functions
prisma/- Database schema and migrations

Installation

  1. Clone repository git clone https://github.com/ValentinZoia/chatapp-backend.git
  2. Install dependencies npm install
  3. Setup environment variables cp .env.example .env.development
  4. Configure .env.development DATABASE_URL, REDIS_URL, JWT_SECRET
  5. Run database migrations npm run prisma:migrate
  6. Optional Seed database: npm run prisma:seed
  7. Start development server npm run start:dev (before start all docker services)
  8. Start all services with Docker docker-compose up -d

Learnings

While building this app, I gained deep hands-on experience with several advanced concepts that textbooks rarely cover. Authentication & Security Implementing a complete refresh token rotation system with HTTP only cookies taught me about CSRF protection, cookie security flags (SameSite, Secure, HttpOnly). GraphQL Deep Dives Working with GraphQL subscriptions reveled challenges I had't anticipated. Setting up Redis Pub/Sub for scaling WebSockets connections across multiple instances meant understanding separete publisher/subscriber connections and implementing reconnection strategies. I also learned how implementing a cursor-based pagination for messages, that is essential for real-time chat applications. Custom GraphQL scalars (like DateTime and Upload) taugght me how to transform data at the schema level, and implementing file uploads via streams helped me understand Node.js streams deeply. Error Handling in GraphQL This was a game-changer. Unlike REST where HTTP status codes communicate errors, GraphQL requires a completely different approach. I built a custom GraphQlExceptionFilter that transforms HTTP exceptions into structured GraphQL errors with correlation IDs-essential for debugging in production. Learning to differentiate between programming errors (that should hide details from users) and operational errors (validation failures, auth issues) was crucial. Observability Implementing Winston logging with environment-aware formats (JSON in production, human-readable in dev) taught me about strucutred logging. Adding correlation ID propagation through all requests (via middleware and GraphQL context) that allow me to trace requests across services easily, improving debugging and monitoring and gave me a clearly idea for the ExecutionContext and his lifecycle (Interceptors -> Apollo Plugins -> Resolver). The Apollo plugin system for logging the entire GraphQL request lifecycle, have a entire Full control over GraphQL l... (line truncated to 2000 chars)

Challenges

  • I encountered several real obstacles that forced me to rethink parts of the architecture and implementation. One of the first challenges I faced was handling real-time presence at scale. Tracking which users are online in each chatroom initially seemed straightforward, but the complexity quickly appeared once I considered multiple server instances. In a horizontally scaled environment, a user connected to Server A wouldn't be visible to someone connected to Server B, which breaks the illusion of real-time presence. To solve this, I implemented Redis Sets to track online users per chatroom. When a user connects via WebSocket, their ID is added to a Redis set keyed by the chatroom ID. Because all server instances query this centralized data source, the distributed state problem is effectively resolved. The trade-off is a slight latency on presence updates, but in return I get consistency across all instances, which is far more important in a distributed system. Another important issue appeared when implementing rate limiting for the GraphQL API. NestJS's built-in throttler works very well for REST endpoints, but GraphQL introduces a different constraint: everything goes through a single /graphql endpoint. This means that, by default, all operations share the same rate limit bucket, which becomes a major vulnerability. For example, a user could exhaust the limit with harmless queries and block legitimate mutations. To address this, I built a custom GraphQLThrottlerGuard that inspects the operation type—Query, Mutation, or Subscription—and generates rate-limit keys based on both the client's IP address and the authenticated user ID. This approach allows different limits for different operation types while also preventing authenticated users from bypassing limits anonymously. As the system started supporting real-time updates, I ran into another subtle problem related to GraphQL subscriptions. Since subscriptions maintain persistent co... (line truncated to 2000 chars)

Screenshots