This document outlines the strategic refactoring roadmap for the Twitch Chat Visualizer project. The objective is to transform the existing monolithic legacy application (CommonJS, Vanilla JS, Mustache templates, stateful Express/Socket.io) into a highly scalable, production-grade 2026-standard monorepo. The target architecture will leverage a pnpm workspaces monorepo containing a NestJS (Fastify) backend and a Vite + React 19 + TypeScript frontend. This modernization will resolve critical security flaws (XSS), severe memory leaks, and performance bottlenecks, ultimately providing a maintainable, type-safe, and highly observable platform ready for enterprise-level scaling.
Current Stack: NestJS 11 (Fastify), React 19, Vite, Tailwind CSS v4, Socket.IO, pnpm workspaces, Redis, Docker (Node 22). Architecture: Modern Monorepo containing a stateless Backend (API Gateway) and a React SPA Frontend. Codebase Health Score: 9/10
Key Deficiencies Summary:
- Zero Type Safety: Resolved. 100% TypeScript.
- Stateful Backend: Resolved. Singleton services and Redis Cache.
- Security: Resolved. Strict sanitization applied on backend messages.
- Resource Management: Resolved. Caching layers active and connection pools managed by NestJS.
| File/Module | Severity | Issue Category | Description | Recommendation (2026 Standard) |
|---|---|---|---|---|
public/assets/js/script.js, transparent.js |
Critical | Security Flaws | XSS Vulnerability: Chat messages (messageObject.message) are injected directly into the DOM via insertAdjacentHTML without HTML escaping or sanitization. |
Migrate to React 19 which inherently escapes strings, and use DOMPurify if rendering raw HTML (e.g., for emotes). |
src/app/controllers/ChatController.js, SocketController.js |
Critical | Code Errors / Memory Leak | Dangling Connections: startChat instantiates a new tmi.Client connection to Twitch per user socket. When the socket disconnects, the TMI client is never destroyed, causing massive memory and connection leaks. |
Implement a centralized Twitch Connection Manager service (Singleton) in NestJS that shares a single tmi.js client across all sockets listening to the same channel. |
src/app/controllers/RequestsController.js |
High | Performance | Missing Caching / N+1 Calls: requestChannelAssets makes 6 synchronous external API calls (Twitch, BTTV, FFZ) per user connection. This will quickly trigger API rate limits and slow down overlay loading. |
Introduce Redis caching for channel assets (badges, emotes) with a TTL (e.g., 1 hour). Use @nestjs/cache-manager. |
src/app/middlewares/ClientMiddleware.js |
High | Scalability / Logic Bug | Global State Mutation: Token logic mutates global.access_token on HTTP requests. If the token expires, persistent WebSockets relying on this token will fail because they bypass HTTP middleware. |
Store the App Access Token in Redis with a background worker (cron) responsible for refreshing it before expiration. Remove global usage. |
package.json, .eslintrc (missing) |
Medium | Code Quality / Tech Debt | Outdated Tech: CommonJS, moment.js (deprecated), Mustache templates, no TypeScript, no linting, huge functions. |
Migrate to TypeScript, replace moment with date-fns or native Intl, and enforce strict ESLint/Prettier rules in a shared config package. |
| Codebase-wide | Medium | Testing Gaps | Zero Tests: Complete lack of unit, integration, and E2E tests. | Implement Jest/Vitest for backend services and Playwright for frontend E2E testing. |
Dockerfile, docker-compose.yaml |
Low | CI/CD & DevOps | Basic DevOps: Missing CI/CD pipelines, using older Node 18, single-stage Dockerfile. | Implement GitHub Actions for CI/CD, use multi-stage Docker builds com Node 22 LTS, e provisionamento de infraestrutura as code usando Pulumi na AWS (Free Tier). |
The project will transition to a Domain-Driven Design (DDD) structured monorepo using pnpm.
graph TD
subgraph pnpm Monorepo
subgraph apps
Web[apps/web<br>Vite + React 19 + TS]
API[apps/api<br>NestJS 11 + Fastify + TS]
end
subgraph packages
Shared[packages/shared<br>Zod schemas, Types]
UI[packages/ui<br>Tailwind + Shadcn]
Config[packages/config<br>ESLint, TSConfig]
end
end
subgraph Infrastructure
Redis[(Redis Cache)]
TwitchAPI[Twitch / BTTV / FFZ APIs]
TwitchChat[Twitch IRC / TMI]
end
Web <-->|WebSockets / tRPC| API
API <-->|Fetch Assets| Redis
API <-->|Query| Database[(PostgreSQL)]
Redis <-->|Cache Miss| TwitchAPI
API <-->|Single Shared Connection per Channel| TwitchChat
Web -.-> Shared
API -.-> Shared
Web -.-> UI
- Backend: NestJS 11 (Fastify Adapter), PostgreSQL (Neon/RDS Free Tier), Drizzle ORM, Zod, Redis (ElastiCache Free Tier), BullMQ (Background Jobs), Socket.io (or native WebSockets).
- Frontend: React 19, Vite, TanStack Router, TanStack Query, Zustand (State Management), React Hook Form + Zod, TailwindCSS v4, Radix UI Primitives / Shadcn UI.
- Infrastructure (AWS via Pulumi): AWS EC2 (t4g.micro / Free Tier) or AWS App Runner, AWS RDS (PostgreSQL Free Tier), AWS ElastiCache (Redis Free Tier).
- Tooling: pnpm workspaces, TypeScript 5.5+, Vitest, GitHub Actions, Docker (multi-stage).
We will use a Strangler Fig Pattern combined with Branch by Abstraction. Since the current app is relatively small, the frontend and backend can be decoupled incrementally:
- Wrap the existing Express app behind a reverse proxy (e.g., Nginx or directly within the new NestJS app as a fallback).
- Stand up the new React frontend and route new traffic there, pointing to the legacy backend initially.
- Migrate API endpoints and WebSocket namespaces one by one to NestJS.
- Once all traffic is routed to NestJS, decommission the Express/Mustache monolith.
- Security: Sanitize inputs in
public/assets/js/script.jsandtransparent.jsusing a lightweight sanitizer to patch the XSS vulnerability. - Stability: Add
client.disconnect()inSocketController.jsinside thedisconnectmethod to stop the immediate TMI client memory leak. - Linting: Add a basic
.prettierrcand.eslintrcto stabilize the current code format.
- Initialize
pnpmworkspace. - Move the existing legacy codebase into
apps/legacy-express. - Scaffold
apps/api(NestJS) andapps/web(Vite + React). - Scaffold
packages/sharedfor common DTOs and interfaces. - Setup GitHub Actions CI pipeline (lint, build).
- NestJS Implementation: Create the
TwitchService,EmoteCacheService, andChatGateway(WebSockets). - Resource Optimization: Implement Redis to cache Twitch, BTTV, and FFZ emotes.
- Connection Pooling: Implement a singleton connection manager in NestJS to ensure only one
tmi.jsclient connects per Twitch channel, regardless of how many users are viewing that channel's overlay. - Token Management: Implement a background Cron service to handle
App Access Tokenrotation securely in Redis.
- Recreate the overlay UI using React 19 and Tailwind CSS.
- Implement React contexts/hooks for Socket.io state management.
- Ensure 100% parity with the configuration menu (
/transparentview settings). - Setup TanStack Router for clean client-side routing.
- Integrate structured logging (Pino) in NestJS.
- Add Health Checks (
@nestjs/terminus). - Create multi-stage
Dockerfilesfor bothapps/apiandapps/web. - Update
docker-compose.yamlto include Redis and the new services.
- Reroute all traffic completely to
apps/webandapps/api. - Delete
apps/legacy-express. - Finalize documentation (README, Architecture Decision Records).
| Risk | Impact | Mitigation / Rollback |
|---|---|---|
| Twitch API Rate Limits during migration | High | The legacy system has no caching. Implement Redis in NestJS first. Rollback: Fallback to legacy app via reverse proxy. |
| Overlay downtime for existing OBS users | Critical | Users have hardcoded overlay URLs in OBS. We must maintain exact URL parity (/:channel/transparent?settings...). Route matching will be strictly tested. |
| Socket connection drops | Medium | Implement robust reconnection logic in the new React frontend. Keep the legacy Express container running until WebSockets are proven stable in staging. |
-
Zero vulnerabilities reported by
pnpm auditand static analysis (SonarQube/ESLint). -
100% Type Coverage: No
anytypes; all API contracts shared viapackages/shared. - Performance: Sub-50ms API response times (thanks to Redis). Single TMI connection per channel.
- Test Coverage: >80% unit test coverage (Vitest) and critical paths covered by E2E tests (Playwright).
- CI/CD: Automated builds, tests, and Docker image publishing via GitHub Actions.
-
Stateless: Application can be scaled horizontally to
$N$ instances without dropping state or duplicating Twitch IRC connections (using Redis Pub/Sub for socket broadcasting).
- Recommended Tech Radar (2026):
- Runtime: Node.js 22 LTS
- Package Manager:
pnpmv9+ - Infrastructure as Code:
Pulumi(TypeScript) targeting AWS Free Tier - Frontend UI:
TailwindCSS v4,Radix UI,shadcn/ui,lucide-react - Frontend State & Data:
TanStack Query,TanStack Router,Zustand(Global State) - Frontend Forms:
React Hook Form+Zod(Validation) - Backend DB & ORM:
PostgreSQL,Drizzle ORM - Backend Queues:
BullMQ(com Redis)
- Reference Docs: