git clone <repository-url>
cd Bookipicd backend
npm installCreate a .env file in the backend directory:
REDIS_HOST=localhost
REDIS_PORT=6379
REDIS_PASSWORD=yourpassword
POSTGRES_USER=postgres
POSTGRES_PASSWORD=postgres3
POSTGRES_DB=BOOKIPI
JWT_SECRET=your-jwt-secret-keycd Frontend
npm installCreate a .env file in the Frontend directory with your API base URL.
VITE_SECRET=yoursecret
cd backend
docker-compose up -dThis will start PostgreSQL and Redis containers.
Backend:
cd backend
npm run start:devThe API will be available at http://localhost:3000
Frontend:
cd Frontend
npm run start:devThe web application will be available at http://localhost:5173
A full-stack e-commerce application with flash sales functionality, built with NestJS backend and React frontend.
Sequence Diagram: Creating an Order

- Create a new user by calling this endpoint localhost:{port}/users/create
- Body
{
"username": "testuser",
"password": "testpassword",
"email": "test@test.com"
}- Login by calling this endpoint localhost:{port}/users/signin
- Body
{
"username": "testuser",
"password": "testpassword"
}- Create a product by calling this endpoint localhost:{port}/orders/create
- Body. This doesn't need the access token because this is supposed to be handled by an admin
{
"name": "AirPod Max",
"stock": 50,
"price": 50
}- Create a flash sale by calling this endpoint localhost:{port}/flash-sales/create
- Body. This also do not require an access token, because this is supposed to be taken care by an admin, but for this app, we wont be catering for an admin.
The access token is automatically added in the header in the Frontend app.
{
"startDate": "2025-09-28",
"endDate": "2025-09-28",
"productId": "fc89a914-18d7-47da-8f96-93ea347918b1"
}- To create an order paste the access token from the signed in user response in the header and call localhost:{port}/orders/create endpoint
- Body. The product ID is the id of the product created initially.
{
"productId": "fc89a914-18d7-47da-8f96-93ea347918b1"
}- Flash sale period: The flashsale period is configurable by calling this patch endpoint localhost:3000/flash-sales/cd6adeb9-b513-4182-9cd5-53d945c90de5
- Body
{
"startDate": "2025-09-28",
"endDate": "2025-09-28"
}-
To ensure that order can only be bought within the start and end date window, the application uses a flashSaleGuard to enforce this rule and returns an error accordingly
-
Single Product, Limited stock: Even though the application allow for creation of several products, it only returns the latest created product.
-
One Item Per user: The application only allows one product per user by checking the order db againt the current product and the userId
API Server
- Endpoint to check the status of the flashsale. This is handled by checking the present day against the flashsale, startDate and endDate and it is displayed to the user on the UI
- EndPoint for a user to attempt a purchase. The create order does some validation before allowing the user make a purchase request.
- The get localhost:3000/orders/ endpoint checks for the for the user purchased order. Frontend
- A user can see the status of their purchase
- A user sees a feedback on if their purchase attempt was successful.
- The current status of the flash sale is displayed for the user to see, also the duration and a countdown timer.
High Concurrency Handling
-
Initial Cache Check (Optimistic): Before hitting the database, the service first checks the stock count in Redis. It uses Redis's atomic DECR command.
-
If the result is less than 0, the request is rejected immediately without touching the database. This filters out the majority of requests when stock is depleted.
-
Start Database Transaction: If the cache check passes, the service begins a database transaction to ensure atomicity.
-
Pessimistic Locking: The service reads the product's stock count from the database using a pessimistic lock (e.g., SELECT stock FROM products WHERE id = :productId FOR UPDATE). This lock prevents any other transaction from reading or writing to that specific row until the current transaction is completed.
-
Final Validation: The code checks if the stock read from the database is > 0. Commit or Rollback: If stock > 0, the service creates the Order record and executes an UPDATE products SET stock = stock - 1 .... It then commits the transaction, releasing the lock.
-
If stock <= 0 (because another transaction got the lock first), the service rolls back the transaction and returns an "Out of Stock" error. It would also update Redis to reflect the zero stock to prevent future database hits.
-
The application uses Redis and Bull queue to handle high concurrency during flash sales.
-
Order Processing: Orders are processed asynchronously using a job queue to ensure reliability and performance
-
User Authentication: Users can register and login to the application. JWT is used for authentication.
Stress Test
- The application was stress tested using Artillery. You can find the script in the root directory named flashsale.yml. To perform stress test
cd Backend
artillery run stress.yml --output test-report.json- Product Management: Create, read, update, and delete products
- Flash Sales: Time-limited sales events with special pricing
- User Authentication: JWT-based authentication system
- Order Processing: Complete order management with queue processing
- Real-time Caching: Redis integration for improved performance
- Purchase Tracking: Comprehensive purchase history and analytics
- Framework: NestJS with TypeScript
- Database: PostgreSQL with TypeORM
- Cache: Redis
- Queue: Bull (Redis-based job queue)
- Authentication: JWT
- Validation: Class Validator & Joi
- Framework: React 18 with TypeScript
- Build Tool: Vite
- UI Library: React Bootstrap
- State Management: React Query
- Forms: React Hook Form with Zod validation
- Styling: Styled Components
- Routing: React Router DOM
Bookipi/
├── backend/ # NestJS API server
│ ├── src/
│ │ ├── common/ # Shared utilities and constants
│ │ ├── fash-sales/ # Flash sales module
│ │ ├── infrastructure/ # Database, cache, and guards
│ │ ├── orders/ # Order management
│ │ ├── products/ # Product management
│ │ ├── purchases/ # Purchase tracking
│ │ └── users/ # User management and auth
│ └── docker-compose.yml # Database and Redis setup
└── Frontend/ # React client application
└── src/
├── components/ # Reusable UI components
├── contexts/ # React contexts
├── hooks/ # Custom hooks
├── pages/ # Route components
└── apis/ # API integration
- Node.js (v16 or higher)
- Docker and Docker Compose
- npm or yarn
POST /users/register- User registrationPOST /users/login- User login
GET /products- List all productsPOST /products- Create new productGET /products/:id- Get product by IDPUT /products/:id- Update productDELETE /products/:id- Delete product
GET /flash-sales- List active flash salesPOST /flash-sales- Create flash saleGET /flash-sales/:id- Get flash sale details
POST /orders- Create new orderGET /orders- List user ordersGET /orders/:id- Get order details
cd Frontend
npm run lint # ESLint checks- You may not need to run migration because I made these properties true for testing purposes in prod this should be made false.
autoLoadEntities: true,
synchronize: true,This project is licensed under the UNLICENSED License.
