Skip to content

Architecture

Utsha Basak edited this page Oct 1, 2026 · 2 revisions

Architecture

The pieces

Browser ──HTTPS──► Express API (/api) ──► MongoDB Atlas
   ▲                    │
   └──── Socket.IO ─────┘   live chat messages and notifications
  • Client: React single-page app in client/, built by Vite. All data fetching goes through TanStack Query hooks in client/src/hooks/queries.ts.
  • API: Express in server/, one router and one controller per area: auth, books, catalogue, cart, wishlist, orders, returns, reviews, chat, notifications, users.
  • Same origin: in production one process serves both the API (under /api) and the built site. The refresh-token cookie depends on this; split across two hosts, it would not survive.

One contract for both sides

server/shared/api.d.ts declares every request and response shape. The server and the client (as @shared/*) both compile against it, so a response cannot change on one side without the other failing to type-check. Every request is also validated on arrival by a Zod schema in server/schemas/.

Where the rules live

The server decides anything that costs money or grants access; pages only show what it will accept.

Rule File
Delivery charge, return window, seller fee, who may move or cancel an order server/config/commerce.ts
Discounts and sale prices server/config/pricing.ts
Promo codes server/config/promotions.ts
What shoppers are told (the same figures, for display) client/src/config/site.ts
The categories client/src/config/categories.ts

When you change a rule, change both the server file and site.ts.

Sign-in

POST /auth/signin returns a short-lived access token (15 minutes), sent as Authorization: Bearer …, and sets a refresh token in an httpOnly cookie (30 days). The client renews the access token on its own when it runs out. Roles are user and admin; selling is decided by owning the book, not by a role.

Real-time

One Socket.IO connection per signed-in tab, authenticated with the access token. Each user is placed in their own room. The server pushes two events:

Event Carries
receive_message A new chat message
notification A new item for the bell

Browsers never send over it. A message is saved with POST /chat/message and then delivered.

Data

One collection per model: users, books (addbooks), carts, wishlists, orders (one document per book in an order, grouped by orderNumber), returns, reviews and seller ratings with their reports, chat messages, notifications (kept 90 days), and refresh tokens.

Covers and photos go straight from the browser to Cloudinary when it is configured; otherwise they are stored inline.

Tests

Server Client
Runner Vitest + Supertest Vitest + Testing Library
Database A real in-memory MongoDB none

server/tests/regressions.test.ts holds one case for every bug that actually shipped. If it fails, an old bug is back.

Clone this wiki locally