DEV Community

Ravi Bhuvan
Ravi Bhuvan

Posted on

Project explaination for my Dockerized-Todo

Project

High-Level Summary

This project is a Full-Stack, Containerized Task Management Web Application designed with a modern microservices architecture. It features a React SPA frontend, a Node.js/Express REST API backend, and an in-memory Redis database for high-performance data persistence. The entire application is fully containerized using Docker and orchestrated locally with Docker Compose, as well as being production-ready for Kubernetes deployments.

Architect and tech stack

Frontend

  • Built with React - Vite, and TailwindCSS (v4).
  • Uses client-side UUID generation (uuidv4) for optimistic item keys.
  • Optimized via a multi-stage Docker build
    • Stage 1 compiles static assets using Node 18 Alpine
    • Stage 2 serves compiled production assets via a lightweight Nginx Alpine web server (reducing image size from ~300MB to ~25MB)

Backend

  • Node.js with Express.js REST API.
  • Handles standard CRUD operations via JSON payloads.
  • Configured with CORS handling cross-origin requests dynamically based on the environment (development vs production).

Database Layer

  • Redis (in-memory key-value data store).
  • Uses Redis Hashes (HSET, HGETALL, HDEL) stored under the hash key "todos". This provides O(1) time complexity for individual item lookups, updates, and deletions.

Infrastructure & Containerization

  • Docker Compose: Orchestrates local multi-container setup (frontend, backend, redis) with network links and dependency checks (depends_on).
  • Kubernetes Ready: Contains declarative YAML manifests (deployment.yaml , service.yaml) configuring Deployment controllers and Services for deployment to clusters (Minikube, EKS, GKE, etc.).
EndPoints
GET           | /todo        | Fetch all tasks
POST          | /todo        | Create new task
PUT           | /todo/:id    | Update task content/status
DELETE        | /todo/:id    | Delete specific task 
POST          | /todo/clear  | Flush all tasks
Enter fullscreen mode Exit fullscreen mode

1.Why Redis instead of PostgreSQL/MongoDB for a Todo app?

• Redis was chosen for lightning-fast reads and writes with minimal overhead. Since all tasks fit into key-value hashes, Redis offers sub-millisecond retrieval. For production durability, Redis can be configured with AOF (Append-Only File) or snapshotting.

2.In docker-compose.yml, how do containers communicate with each other?

  • When Docker Compose boots up services declared in docker-compose.yml, it automatically creates a default isolated bridge network. Docker runs an internal DNS server on that network, allowing containers to resolve each other using their service names as hostname aliases:
    • The backend container connects to Redis via REDIS_URL=redis://redis:6379, where redis resolves directly to the container IP of the redis service.
    • The browser accesses the frontend on host port 3000, which forwards to Nginx container port 80.

3.How did you configure CORS in Express, and how does it change between Development and Production?

In server.js, CORS is dynamically configured based on process.env.NODE_ENV:

  • In Development: Allowed origins include http://localhost:3000, http://localhost:5173, and 127.0.0.1:5173 to support Vite dev server and local containers.
  • In Production: Origins are restricted strictly to process.env.FRONTEND_URL.
  • I also explicitly enabled HTTP methods (GET, POST, PUT, DELETE) and allowed header credentials.

4.Walk me through your Kubernetes architecture (backend-deployment.yaml, frontend-deployment.yaml, redis-deployment.yaml).

The Kubernetes setup consists of 3 distinct deployments and matching services:

  • Redis: Deployed as a single replica Deployment with a ClusterIP service exposing internal port 6379 (redis-service).
  • Backend: Deployed as a 2-replica Deployment for scalability. It connects to Redis using the internal DNS name redis-service:6379. Exposed internally via a ClusterIP or NodePort service (backend-service).
  • Frontend: Deployed with Nginx serving static files. Exposed via a NodePort (or IngressController in cloud production) to route external user web traffic.

5.How would you scale this application to handle 100,000 active concurrent users?

  • Stateless Backend Scaling: Scale the Node.js backend to 10+ pods behind a Kubernetes Ingress / AWS ALB load balancer.
  • Redis Cluster: Move from single-node Redis to a Redis Cluster with primary-replica replication and sharding (or AWS ElastiCache for Redis).
  • CDN Integration: Serve static frontend assets via Cloudflare or AWS CloudFront edge CDNs instead of hitting Nginx pods directly.
  • Caching Layer / Rate Limiting: Implement API rate limiting (express-rate-limit) to prevent DDoS attacks.

6.Redis is an in-memory datastore. Is data lost if the Redis container restarts? How would you make it persistent?

  • By default, Redis stores data in RAM, so restarting the container without persistence configured will wipe the data.
    • we can Enable Redis persistence mechanisms: RDB (Redis Database Snapshots) or AOF (Append Only File) in redis.conf.

7.What is "Stateless"? How is it implemented in this project?

  • A service is stateless if it does not store any user data or session history inside its own server memory or local disk
    • The backend code in todoOperations.js has no global array variables or in-memory session objects. Every single incoming request (GET, POST, PUT, DELETE) immediately reads from or writes to Redis.
    • In Dockerfile, Nginx merely serves static compiled files (index.html, JavaScript, CSS).The web server does not store user data; all application state lives inside the user's browser (React component state) or in the database.

Top comments (0)