Skip to content

almat101/secure_e-commerce

Repository files navigation

Mini CRUD App - E-Commerce Microservices Platform

A full-stack e-commerce/marketplace application with event-driven microservices architecture, built to demonstrate modern backend patterns, asynchronous communication with Redis Streams, and secure deployment practices.


πŸ“‹ Table of Contents

  1. Architecture Overview
  2. Project Structure
  3. Backend Services
  4. Redis Streams - Event-Driven Communication
  5. Frontend
  6. Infrastructure
  7. Design Patterns
  8. Security
  9. Testing
  10. How to Run
  11. Next Implementations

πŸ—οΈ Architecture Overview

This project implements a microservices architecture where services communicate through Redis Streams for asynchronous, reliable message passing.

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚   Frontend   β”‚ (React + Bootstrap)
β”‚  (Port 8080) β”‚
β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”˜
       β”‚
       β”‚ HTTP/REST
       β”‚
β”Œβ”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                  Docker Network (crud-app)                β”‚
β”‚                                                            β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”    β”‚
β”‚  β”‚Auth-Service β”‚  β”‚Products-     β”‚  β”‚Orders-       β”‚    β”‚
β”‚  β”‚(Express)    β”‚  β”‚Service       β”‚  β”‚Service       β”‚    β”‚
β”‚  β”‚Port 3030    β”‚  β”‚(Express)     β”‚  β”‚(Fastify+TS)  β”‚    β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”˜  β”‚Port 3020     β”‚  β”‚Port 3040     β”‚    β”‚
β”‚         β”‚         β””β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”˜  β””β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”˜    β”‚
β”‚         β”‚             β”‚      β”‚          β”‚      β”‚         β”‚
β”‚    β”Œβ”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”   β”Œβ”€β”€β”€β”€β–Όβ”€β”€β”  β”‚     β”Œβ”€β”€β”€β”€β–Όβ”€β”€β”   β”‚         β”‚
β”‚    β”‚db_auth  β”‚   β”‚db_    β”‚  β”‚     β”‚db_    β”‚   β”‚         β”‚
β”‚    β”‚(PG)     β”‚   β”‚productsβ”‚  β”‚     β”‚orders β”‚   β”‚         β”‚
β”‚    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   β”‚(PG)   β”‚  β”‚     β”‚(PG)   β”‚   β”‚         β”‚
β”‚                  β””β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚     β””β”€β”€β”€β”€β”€β”€β”€β”˜   β”‚         β”‚
β”‚                             β”‚                 β”‚         β”‚
β”‚                   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”  β”‚
β”‚                   β”‚         Redis Streams            β”‚  β”‚
β”‚                   β”‚  β€’ orders_stream                 β”‚  β”‚
β”‚                   β”‚  β€’ inventory_stream              β”‚  β”‚
β”‚                   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚
β”‚                                                          β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Key Technologies

Component Technology
Frontend React, React Router, Bootstrap 5, Axios
Auth-Service Express.js, PostgreSQL, JWT, bcrypt, Joi
Products-Service Express.js, PostgreSQL, Redis (Consumer)
Orders-Service Fastify, TypeScript, PostgreSQL, Drizzle ORM, Redis (Publisher)
Message Broker Redis Streams (ioredis)
Databases PostgreSQL 17 (3 separate databases)
Containerization Docker, Docker Compose
CI/CD GitHub Actions, Ansible
Tunnel Cloudflare Tunnel

πŸ“ Project Structure

mini_crud_app/
β”œβ”€β”€ backend/
β”‚   β”œβ”€β”€ auth-service/              # Authentication & User Management
β”‚   β”‚   β”œβ”€β”€ controllers/           # HTTP request handlers
β”‚   β”‚   β”œβ”€β”€ services/              # Business logic layer
β”‚   β”‚   β”œβ”€β”€ repositories/          # Database access layer
β”‚   β”‚   β”œβ”€β”€ routes/                # Route definitions
β”‚   β”‚   β”œβ”€β”€ config/                # Database & JWT config
β”‚   β”‚   β”œβ”€β”€ __tests__/             # Unit & Integration tests
β”‚   β”‚   β”‚   β”œβ”€β”€ unit/
β”‚   β”‚   β”‚   └── integration/
β”‚   β”‚   β”œβ”€β”€ init-scripts/          # Database initialization
β”‚   β”‚   β”œβ”€β”€ server.js
β”‚   β”‚   β”œβ”€β”€ app.js
β”‚   β”‚   └── Dockerfile
β”‚   β”‚
β”‚   β”œβ”€β”€ products-service/          # Product Catalog & Inventory
β”‚   β”‚   β”œβ”€β”€ consumer/              # Redis Stream consumer
β”‚   β”‚   β”‚   └── ordersConsumer.js  # Processes orders from orders_stream
β”‚   β”‚   β”œβ”€β”€ redis/                 # Redis client configuration
β”‚   β”‚   β”œβ”€β”€ db/                    # PostgreSQL connection pool
β”‚   β”‚   β”œβ”€β”€ init-scripts/          # Database initialization
β”‚   β”‚   β”œβ”€β”€ products.js            # Main service file
β”‚   β”‚   └── Dockerfile
β”‚   β”‚
β”‚   └── orders-service/            # Order Processing (TypeScript)
β”‚       β”œβ”€β”€ src/
β”‚       β”‚   β”œβ”€β”€ routes/            # Fastify route handlers
β”‚       β”‚   β”œβ”€β”€ services/          # Business logic + Redis publisher
β”‚       β”‚   β”œβ”€β”€ db/                # Drizzle ORM schema & client
β”‚       β”‚   β”œβ”€β”€ redis/             # Redis client configuration
β”‚       β”‚   β”œβ”€β”€ middleware/        # JWT authentication hook
β”‚       β”‚   β”œβ”€β”€ types/             # TypeScript interfaces
β”‚       β”‚   β”œβ”€β”€ app.ts             # Fastify app configuration
β”‚       β”‚   └── server.ts          # Server startup
β”‚       β”œβ”€β”€ drizzle/               # Drizzle ORM migrations
β”‚       β”œβ”€β”€ init-scripts/          # Database initialization
β”‚       β”œβ”€β”€ tsconfig.json
β”‚       └── Dockerfile
β”‚
β”œβ”€β”€ frontend/                      # React SPA
β”‚   β”œβ”€β”€ src/
β”‚   β”‚   β”œβ”€β”€ components/            # React components
β”‚   β”‚   β”‚   β”œβ”€β”€ BSNavbar.js
β”‚   β”‚   β”‚   β”œβ”€β”€ CartPage.js
β”‚   β”‚   β”‚   β”œβ”€β”€ MyHome.js
β”‚   β”‚   β”‚   β”œβ”€β”€ ProductPage.js
β”‚   β”‚   β”‚   β”œβ”€β”€ LoginForm.js
β”‚   β”‚   β”‚   └── SignupForm.js
β”‚   β”‚   β”œβ”€β”€ context/               # React Context API
β”‚   β”‚   β”‚   β”œβ”€β”€ AuthContext.js     # Authentication state
β”‚   β”‚   β”‚   └── CartContext.js     # Shopping cart state
β”‚   β”‚   β”œβ”€β”€ interceptor/           # Axios interceptors
β”‚   β”‚   β”œβ”€β”€ App.js
β”‚   β”‚   └── index.js
β”‚   β”œβ”€β”€ public/
β”‚   β”œβ”€β”€ conf/                      # Nginx configuration
β”‚   β”œβ”€β”€ Dockerfile
β”‚   └── package.json
β”‚
β”œβ”€β”€ ansible/                       # Deployment automation
β”œβ”€β”€ .github/workflows/             # CI/CD pipelines
β”‚   └── deploy.yml
β”œβ”€β”€ docker-compose.yml
β”œβ”€β”€ docker-compose.dev.yml
└── .env.example

πŸ”§ Backend Services

1️⃣ Auth-Service

Port: 3030 (internal)
Database: db_auth (PostgreSQL)
Framework: Express.js
Architecture: Controller-Service-Repository Pattern

Responsibilities

  • User registration and authentication
  • JWT token generation and validation
  • Password hashing with bcrypt
  • Input validation with Joi

Database Schema (users table)

CREATE TABLE users (
    id SERIAL PRIMARY KEY,
    username VARCHAR(50) UNIQUE NOT NULL,
    email VARCHAR(255) UNIQUE NOT NULL,
    password_hash VARCHAR(255) NOT NULL,
    role VARCHAR(20) DEFAULT 'USER' NOT NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

Key Endpoints

Method Endpoint Description
POST /api/auth/signup Register new user
POST /api/auth/login Login with credentials
POST /api/auth/demo-login Demo login (for testing)

Code Structure

Controllers (authController.js)

  • Handle HTTP requests/responses
  • Delegate business logic to services
  • Return appropriate HTTP status codes

Services (authService.js)

  • Business logic (validation, hashing, token generation)
  • Independent of HTTP layer (reusable)

Repositories (authRepository.js)

  • Database queries (CRUD operations)
  • Connection pool management

Tests

  • βœ… Unit Tests: Test individual functions (services/repositories)
  • βœ… Integration Tests: Test full HTTP request flow
  • Pattern: AAA (Arrange-Act-Assert)

2️⃣ Products-Service

Port: 3020 (internal)
Database: db_products (PostgreSQL)
Framework: Express.js
Redis: Consumer of orders_stream

Responsibilities

  • CRUD operations for products
  • Inventory management (stock quantities)
  • Redis Stream Consumer: Listens to orders_stream and updates inventory

Database Schema (products table)

CREATE TABLE products (
    id SERIAL PRIMARY KEY,
    name VARCHAR(255) NOT NULL,
    price NUMERIC(10, 2) NOT NULL,
    category VARCHAR(100),
    quantity INTEGER DEFAULT 1,
    user_id INTEGER NOT NULL,        -- Seller ID (external reference)
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

Key Endpoints

Method Endpoint Description Auth
GET /api/products/public/ Public catalog (all products) ❌
GET /api/products/ User's products (seller view) βœ…
GET /api/products/my-home/ Products from other sellers βœ…
GET /api/products/:id Get single product βœ…
POST /api/products Create new product βœ…
PATCH /api/products/:id Update product βœ…
DELETE /api/products/:id Delete product βœ…

Redis Stream Consumer

File: consumer/ordersConsumer.js

What it does:

  1. Listens to orders_stream (blocking read with XREAD)
  2. For each order, decrements product quantities in a PostgreSQL transaction
  3. Publishes result to inventory_stream:
    • COMPLETED if all products had sufficient stock
    • FAILED if any product was out of stock

Code Flow:

// Infinite loop listening to orders_stream
while (true) {
  const results = await redis.xread("BLOCK", 0, "STREAMS", "orders_stream", lastId);
  
  for (const msg of messages) {
    await processMessage(msg);  // Update inventory
  }
  
  lastId = messages[messages.length - 1][0];  // Update cursor
}

Transaction Example:

await client.query('BEGIN');
try {
  for (let item of orderProducts) {
    const result = await client.query(
      `UPDATE products 
       SET quantity = quantity - $1 
       WHERE id = $2 AND quantity >= $1
       RETURNING *`,
      [quantity, productId]
    );
    
    if (result.rowCount === 0) {
      orderFailed = true;
      break;
    }
  }
  
  if (!orderFailed) {
    await client.query('COMMIT');
    await redis.xadd("inventory_stream", "*", 
      'order_id', orderID,
      'status', "COMPLETED"
    );
  } else {
    await client.query('ROLLBACK');
    await redis.xadd("inventory_stream", "*",
      'order_id', orderID,
      'status', "FAILED"
    );
  }
} catch (error) {
  await client.query('ROLLBACK');
}

3️⃣ Orders-Service

Port: 3040 (internal)
Database: db_orders (PostgreSQL)
Framework: Fastify + TypeScript
ORM: Drizzle ORM
Redis: Publisher to orders_stream

Responsibilities

  • Create customer orders
  • Store order details in database
  • Redis Stream Publisher: Publishes new orders to orders_stream

Database Schema (Drizzle ORM)

File: src/db/schema.ts

// orders table
export const orders = pgTable("orders", {
  id: integer("id").primaryKey().generatedAlwaysAsIdentity(),
  userId: integer("user_id").notNull(),  // External reference (auth-service)
  totalPrice: decimal("total_price", { precision: 10, scale: 2 }).notNull(),
  status: varchar("status", { length: 50 }).default("PENDING"),
  createdAt: timestamp("created_at").defaultNow(),
});

// order_items table
export const orderItems = pgTable("order_items", {
  id: integer("id").primaryKey().generatedAlwaysAsIdentity(),
  orderId: integer("order_id").notNull()
    .references(() => orders.id, { onDelete: "cascade" }),  // Internal FK
  productId: integer("product_id").notNull(),  // External reference (products-service)
  quantity: integer("quantity").notNull(),
  price: decimal("price", { precision: 10, scale: 2 }).notNull(),
});

Key Endpoints

Method Endpoint Description Auth
POST /api/orders Create new order βœ…
GET /health Health check ❌

Redis Stream Publisher

File: src/services/orderService.ts

What it does:

  1. Receives order data from frontend
  2. Saves order to database in a Drizzle transaction with status PENDING
  3. Publishes order details to orders_stream for inventory processing

Code Flow:

export async function createOrder(
  userId: number,
  totalPrice: string,
  status: "PENDING" | "COMPLETED" | undefined,
  items: IOrderItem[]
): Promise<{ orderId: number; itemCount: number }> {
  
  // 1. Save to database
  const orderId = await db.transaction(async (tx) => {
    const [orderIDReturned] = await tx
      .insert(orders)
      .values({ userId, status, totalPrice })
      .returning({ insertedId: orders.id });
    
    await tx.insert(orderItems).values(
      items.map(item => ({
        orderId: orderIDReturned!.insertedId,
        productId: item.product_id,
        quantity: item.quantity,
        price: item.price
      }))
    );
    
    return orderIDReturned!.insertedId;
  });
  
  // 2. Publish to Redis Stream
  const redis = await getRedisClient();
  await redis.xadd(
    "orders_stream",
    "*",  // auto-generate message ID
    'order_id', orderId.toString(),
    'products', JSON.stringify(items)
  );
  
  return { orderId, itemCount: items.length };
}

πŸ”₯ Redis Streams - Event-Driven Communication

What is Redis Streams?

Redis Streams is a data structure in Redis that acts as a distributed message log (similar to Kafka, but simpler). It's ideal for:

  • Asynchronous communication between microservices
  • Event-driven architecture
  • Persistent message queues

Architecture in This Project

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”          β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”          β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  Orders-Service  β”‚--------->β”‚   Redis     β”‚--------->β”‚ Products-Service β”‚
β”‚   (Producer)     β”‚  XADD    β”‚  Streams    β”‚  XREAD   β”‚   (Consumer)     β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜          β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜          β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                                    β”‚
                                    β”‚
                 β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                 β”‚                                     β”‚
                 v                                     v
      β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”                 β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
      β”‚  orders_stream   β”‚                 β”‚ inventory_stream β”‚
      β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜                 β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
         Published by:                        Published by:
         orders-service                       products-service
         
         Consumed by:                         Consumed by:
         products-service                     orders-service (TODO)

Complete Order Flow

Step 1: User Checkout (Frontend)

// CartPage.js
const orderData = {
  total_price: "1599.98",
  status: "PENDING",
  items: [
    { product_id: 1, price: "999.99", quantity: 1 },
    { product_id: 2, price: "599.99", quantity: 1 }
  ]
};

await axios.post('/api/orders', orderData, { withCredentials: true });

Step 2: Orders-Service Processes Request

  1. Validates the request (Fastify schema validation)
  2. Extracts user_id from JWT (authMiddleware)
  3. Saves order to db_orders with status PENDING
  4. Publishes to orders_stream:
await redis.xadd(
  "orders_stream",
  "*",
  'order_id', '42',
  'products', '[{"product_id":1,"price":"999.99","quantity":1}]'
);

Step 3: Products-Service Consumes Message

The consumer (always running) receives the message:

// Reads from orders_stream
const results = await redis.xread("BLOCK", 0, "STREAMS", "orders_stream", lastId);
// Format: [['orders_stream', [['1767783162611-0', ['order_id', '42', 'products', '[...]']]]]]

// Parses message
const orderData = {
  order_id: '42',
  products: [{ product_id: 1, price: "999.99", quantity: 1 }]
};

Step 4: Inventory Update (Transaction)

await client.query('BEGIN');

for (let item of orderProducts) {
  const result = await client.query(
    `UPDATE products 
     SET quantity = quantity - $1 
     WHERE id = $2 AND quantity >= $1
     RETURNING *`,
    [item.quantity, item.product_id]
  );
  
  if (result.rowCount === 0) {
    // Product not found or insufficient stock
    orderFailed = true;
    break;
  }
}

if (!orderFailed) {
  await client.query('COMMIT');
  console.log("βœ… Inventory updated successfully");
} else {
  await client.query('ROLLBACK');
  console.log("❌ Inventory update failed");
}

Step 5: Publish Result to inventory_stream

if (!orderFailed) {
  await redis.xadd("inventory_stream", "*",
    'order_id', '42',
    'status', 'COMPLETED'
  );
} else {
  await redis.xadd("inventory_stream", "*",
    'order_id', '42',
    'status', 'FAILED'
  );
}

Step 6: Orders-Service Updates Status (TODO)

A consumer in orders-service should read from inventory_stream and update the order status in db_orders.

Benefits of Redis Streams

Benefit Description
βœ… Decoupling Services don't call each other directly (loose coupling)
βœ… Reliability Messages are persisted; no data loss if a service crashes
βœ… Scalability Multiple consumers can read from the same stream
βœ… Event Sourcing Complete audit trail of all events
βœ… Automatic Retry Consumers can resume from last processed message
βœ… Asynchronous Non-blocking operations between services

Redis Commands (For Debugging)

# Enter Redis container
docker exec -it redis redis-cli

# View all messages in orders_stream from the beginning
XREAD STREAMS orders_stream 0

# View all messages with range
XRANGE orders_stream - +

# View last 5 messages
XREVRANGE orders_stream + - COUNT 5

# View stream info (length, first/last message, etc.)
XINFO STREAM orders_stream

🎨 Frontend

Framework: React 18
UI Library: Bootstrap 5
Routing: React Router v6
State Management: Context API

Features

  • Authentication: Login/Signup forms with JWT
  • Product Catalog: Browse products from all sellers
  • My Products: Manage your own products (seller view)
  • Shopping Cart: Add/remove items, update quantities
  • Checkout: Place orders (sends to orders-service)
  • Demo Mode: Test without registration

Context Providers

AuthContext

  • Manages user authentication state
  • Stores JWT token in HTTP-only cookies
  • Provides isAuth, login, logout functions

CartContext

  • Manages shopping cart state (localStorage persistence)
  • Provides addToCart, removeFromCart, updateQuantity, clearCart, getTotal

Key Components

Component Description
MyHome.js Homepage with product catalog
ProductPage.js Manage user's own products
CartPage.js Shopping cart + checkout
LoginForm.js Login interface
SignupForm.js Registration interface
BSNavbar.js Navigation bar with cart icon

🐳 Infrastructure

Docker Compose Services

Service Image Port (Host) Port (Internal) Purpose
frontend Custom (Nginx) 8080 80 React SPA
auth-service Custom (Node) - 3030 Authentication
products-service Custom (Node) - 3020 Product catalog
orders-service Custom (Node) - 3040 Order processing
db_auth postgres:17 - 5432 Auth database
db_products postgres:17 - 5432 Products database
db_orders postgres:17 - 5432 Orders database
redis redis:8.4.0 - 6379 Message broker
cloudflared cloudflare/cloudflared - - Tunnel for HTTPS

Network Isolation

All backend services and databases are isolated within the Docker network (crud-app). Only the frontend (Nginx) is exposed on port 8080.

This follows security best practices:

  • Services communicate via internal DNS names (e.g., http://auth-service:3030)
  • No direct external access to databases or backend APIs
  • Single entry point through Nginx (acts as reverse proxy + static file server)

Health Checks

All services implement health checks for:

  • Ordered startup (depends_on with condition: service_healthy)
  • Auto-restart on failure
  • Monitoring container status

Example:

healthcheck:
  test: ["CMD", "wget", "--spider", "-q", "http://localhost:3030/health"]
  interval: 30s
  timeout: 10s
  retries: 3
  start_period: 5s

Volumes (Data Persistence)

volumes:
  postgres_auth_data:       # Persists user data
  postgres_products_data:   # Persists product catalog
  postgres_orders_data:     # Persists order history
  redis_data:               # Persists Redis streams

🎯 Design Patterns

1. Controller-Service-Repository (CSR)

Used in: auth-service

HTTP Request β†’ Controller β†’ Service β†’ Repository β†’ Database
                   ↓           ↓           ↓
             (HTTP logic) (Business) (SQL queries)

Benefits:

  • Separation of concerns
  • Testability (mock each layer independently)
  • Reusability (services can be used by multiple controllers)

2. Event-Driven Architecture (Choreography)

Used in: Communication between orders-service and products-service

orders-service β†’ orders_stream β†’ products-service
                                      ↓
                               inventory_stream

Benefits:

  • Loose coupling (services don't know about each other)
  • Scalability (add more consumers easily)
  • Fault tolerance (messages persist even if consumers are down)

3. Singleton Pattern

Used in: Database connections, Redis clients

let redisClient = null;

export async function getRedisClient() {
  if (!redisClient) {
    redisClient = new Redis({ ... });
  }
  return redisClient;
}

Benefits:

  • Single connection instance (efficient resource usage)
  • Lazy initialization (connection created only when needed)

4. Transaction Pattern

Used in: All database writes affecting multiple tables

await client.query('BEGIN');
try {
  await client.query('INSERT INTO orders ...');
  await client.query('INSERT INTO order_items ...');
  await client.query('COMMIT');
} catch (error) {
  await client.query('ROLLBACK');
}

Benefits:

  • Atomicity: All operations succeed or all fail
  • Consistency: Database stays in valid state
  • Isolation: Concurrent transactions don't interfere

5. Middleware/Hook Pattern

Used in: JWT authentication

// Express middleware
function JWT_middleware_decode(req, res, next) {
  const token = req.cookies.token;
  if (!token) return res.status(401).json({ message: "Unauthorized" });
  
  const decoded = jwt.verify(token, process.env.JWT_SECRET);
  req.user = decoded;  // Attach user to request
  next();
}

app.get('/api/products', JWT_middleware_decode, (req, res) => {
  // req.user is available here
});
// Fastify hook
server.addHook('preHandler', authMiddleware);

6. Plugin Pattern (Fastify)

Used in: orders-service

// Each route is a plugin
export default async function orderRoutes(server: FastifyInstance) {
  server.post('/api/orders', async (request, reply) => { ... });
}

// Register plugin
server.register(orderRoutes, { prefix: '/api/orders' });

7. Context Pattern (React)

Used in: Frontend state management

const AuthContext = createContext();

export function AuthProvider({ children }) {
  const [isAuth, setIsAuth] = useState(false);
  return (
    <AuthContext.Provider value={{ isAuth, setIsAuth }}>
      {children}
    </AuthContext.Provider>
  );
}

// Usage in components
const { isAuth } = useContext(AuthContext);

πŸ” Security

βœ… Implemented

Security Feature Implementation
JWT Authentication HTTP-only cookies (not localStorage)
Password Hashing bcrypt with salt rounds
SQL Injection Prevention Parameterized queries ($1, $2, etc.)
CORS Configured for specific origins only
Security Headers Helmet.js middleware
Input Validation Joi for auth-service, Fastify schemas for orders-service
Network Isolation Backend services not exposed to internet
Environment Variables Sensitive data in .env (gitignored)

Example: SQL Injection Prevention

// ❌ VULNERABLE (never do this!)
const query = `SELECT * FROM users WHERE email = '${email}'`;

// βœ… SAFE (parameterized query)
const query = 'SELECT * FROM users WHERE email = $1';
const result = await pool.query(query, [email]);

Example: HTTP-only Cookies

// Backend sets cookie
res.cookie('token', jwtToken, {
  httpOnly: true,    // Not accessible via JavaScript
  secure: true,      // HTTPS only
  sameSite: 'strict' // CSRF protection
});

// Frontend automatically sends cookie with requests
axios.post('/api/orders', data, { withCredentials: true });

πŸ§ͺ Testing

Auth-Service (Fully Tested)

Unit Tests

  • Target: Individual functions (services, repositories)
  • Mock: Database connections
  • Pattern: AAA (Arrange-Act-Assert)
  • Runner: Jest

Example:

test("should hash a password", async () => {
  // Arrange
  const plaintextPassword = "test12345";

  // Act
  const hash = await hashPassword(plaintextPassword);

  // Assert
  expect(hash).toBeDefined();
  expect(hash).not.toBe(plaintextPassword);
  expect(await bcrypt.compare(plaintextPassword, hash)).toBe(true);
});

Integration Tests

  • Target: Full HTTP request flow (controller β†’ service β†’ repository)
  • Mock: Database with jest.unstable_mockModule
  • Tool: Supertest

Example:

jest.unstable_mockModule("../../repositories/authRepository.js", () => ({
  createUser: jest.fn(),
  findUser: jest.fn(),
}));

test("POST /api/auth/signup - success", async () => {
  findUser.mockResolvedValueOnce({ rowCount: 0 });  // User doesn't exist
  createUser.mockResolvedValueOnce({ rowCount: 1 });

  const response = await request(app)
    .post('/api/auth/signup')
    .send({ username: 'test', email: 'test@test.com', password: 'test123' });

  expect(response.status).toBe(201);
  expect(response.body.message).toBe('User registered successfully');
});

Products-Service & Orders-Service

  • ⏳ Testing to be implemented

Running Tests

cd backend/auth-service

# Run all tests
npm test

# Run only unit tests
npm run test:unit

# Run only integration tests
npm run test:integration

# Run with coverage
npm run test:coverage

πŸš€ How to Run

Prerequisites

  • Docker and Docker Compose installed
  • Node.js 18+ and npm (for local development)
  • Git for cloning the repository

Steps

1. Clone the Repository

git clone https://github.com/your-username/mini_crud_app.git
cd mini_crud_app

2. Configure Environment Variables

cp .env.example .env

Edit .env and configure:

  • Database credentials (keep defaults for Docker)
  • JWT secret (change in production!)
  • Cloudflare tunnel token (optional, for external access)

3. Start the Application

docker-compose up --build

This will:

  1. Build all service images
  2. Create databases with init scripts
  3. Start Redis
  4. Start backend services
  5. Build and serve frontend

4. Access the Application

  • Frontend: http://localhost:8080
  • Backend Services: Internal only (not exposed)
    • Auth-Service: http://auth-service:3030 (from within Docker network)
    • Products-Service: http://products-service:3020
    • Orders-Service: http://orders-service:3040
    • Redis: redis:6379

5. Demo Credentials

Two demo users are pre-created:

Username Email Password Role
demo demo@demo.com demotest Seller
demo2 demo2@demo.com demotest Buyer

6. Development Mode

For frontend development with hot reload:

cd frontend
npm install
npm start  # Runs on http://localhost:3000

Set REACT_APP_IS_DEV=true in .env to use development API URLs.

7. Stopping the Application

docker-compose down          # Stop containers
docker-compose down -v       # Stop and remove volumes (deletes data!)

πŸ“ Next Implementations

Completed βœ…

  • βœ… JWT-based authentication
  • βœ… HTTP-only cookies for token storage
  • βœ… Redis Streams for event-driven architecture
  • βœ… Database per service (microservices pattern)
  • βœ… Docker containerization
  • βœ… Health checks for all services
  • βœ… Network isolation (security)

In Progress πŸ”„

  • πŸ”„ Inventory Stream Consumer (orders-service): Listen to inventory_stream and update order status
  • πŸ”„ Refactor products-service: Implement Controller-Service-Repository pattern

Planned πŸ“‹

  1. Role-Based Access Control (RBAC)

    • Admin role for user management
    • Permissions system
  2. Rate Limiting

    • Prevent API abuse
    • Per-user quotas
  3. Structured Logging

    • Pino or Winston for JSON logs
    • Correlation IDs for tracing requests across services
    • Integration with Grafana Loki
  4. API Documentation

    • Swagger/OpenAPI for REST endpoints
    • Auto-generated from code
  5. End-to-End (E2E) Tests

    • Playwright or Cypress
    • Full user journey tests
  6. Monitoring & Observability

    • Prometheus metrics export
    • Grafana dashboards
    • Alerting rules
  7. Message Acknowledgment

    • Redis Stream consumer groups
    • Prevent duplicate processing
  8. Dead Letter Queue

    • Handle failed messages
    • Retry logic with exponential backoff
  9. CQRS Pattern

    • Separate read/write models
    • Optimize for query performance
  10. WebSockets

    • Real-time order status updates
    • Live inventory changes

πŸ“š Additional Documentation


πŸ“„ License

This project is licensed under the MIT License. See LICENSE for details.


About

Piattaforma e-commerce con microservizi event-driven: comunicazione asincrona via Redis Streams, architettura scalabile con React, Node.js/TypeScript, PostgreSQL, deployment Docker. Demo di pattern moderni backend.

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors