2026-08-11 14:08:40 +07:00
2026-08-11 13:37:25 +07:00
2026-08-11 13:37:25 +07:00
2026-08-11 13:37:25 +07:00
2026-08-11 13:37:25 +07:00
2026-08-11 13:37:25 +07:00
2026-08-11 13:37:25 +07:00
2026-08-11 13:37:25 +07:00
2026-08-11 13:37:25 +07:00
2026-08-11 13:37:25 +07:00
2026-08-11 13:37:25 +07:00
2026-08-11 14:08:40 +07:00
2026-08-11 13:37:25 +07:00

Sport Store

A modern sports-fashion e-commerce platform.

Status: milestone 0 — architecture skeleton. The structure, boundaries, data model and tooling are in place and verified. Business features are not implemented yet; see Roadmap.

Storefront (Next.js) ─┐
                      ├─▶ REST API (NestJS) ─▶ PostgreSQL · Redis · R2/S3
Admin (Next.js) ──────┘

Quick start

Requires Node ≥ 22, pnpm ≥ 10 (corepack enable) and Docker.

pnpm setup   # env files, real JWT secrets, deps, containers, migrations, seed
pnpm dev     # storefront + admin + API, all watching
Service URL
Storefront http://localhost:3000
Admin http://localhost:3001
API http://localhost:4000/api/v1/health
API docs (Swagger) http://localhost:4000/docs
MinIO console http://localhost:9001 (sportminio / sportminio)
Mailpit http://localhost:8025

Verify the stack is healthy:

curl -s http://localhost:4000/api/v1/health | jq
{
  "success": true,
  "data": {
    "status": "ok",
    "dependencies": {
      "database": { "status": "up", "latencyMs": 3 },
      "redis": { "status": "up", "latencyMs": 1 }
    }
  },
  "meta": { "requestId": "019fef70-a80f-77d5-a975-bbe9f5f0c32c", "timestamp": "…" }
}

Ports. PostgreSQL is published on 5433 and Redis on 6380, not their defaults. Many machines already run one or both, and a shadowed port surfaces as a baffling authentication error rather than a clear conflict. Inside the Docker network they still use 5432/6379.


Commands

Everyday

pnpm dev                  # everything
pnpm dev:storefront       # one app (dependencies are built first)
pnpm dev:admin
pnpm dev:api

pnpm build                # build all, in dependency order
pnpm lint                 # ESLint across the workspace
pnpm lint:fix
pnpm typecheck            # tsc --noEmit everywhere
pnpm test
pnpm format               # Prettier write
pnpm format:check

Infrastructure

pnpm infra:up             # PostgreSQL, Redis, MinIO, Mailpit
pnpm infra:down
pnpm infra:logs
pnpm infra:reset          # destroys volumes, then restarts

docker compose --profile full up --build   # full stack behind Nginx on :80

Database

pnpm db:migrate           # create + apply a migration (dev)
pnpm db:deploy            # apply pending migrations (production)
pnpm db:generate          # regenerate Prisma Client
pnpm db:seed              # reconcile permissions + roles (idempotent)
pnpm db:studio            # Prisma Studio
pnpm db:reset             # drop, re-migrate, re-seed

Running one app manually

pnpm --filter @sport/api        run dev     # :4000
pnpm --filter @sport/storefront run dev     # :3000
pnpm --filter @sport/admin      run dev     # :3001

Repository layout

sport-store/
├── apps/
│   ├── storefront/          Next.js customer site (:3000)
│   │   └── src/
│   │       ├── app/         App Router — (shop) (checkout) (account) route groups
│   │       ├── components/  Cross-feature UI (layout, chrome)
│   │       ├── features/    auth · product · category · collection · search
│   │       │                cart · checkout · order · wishlist · account
│   │       ├── hooks/  lib/  services/  stores/  styles/  types/
│   │
│   ├── admin/               Next.js back office (:3001)
│   │   └── src/
│   │       ├── app/         (auth) login · (dashboard) everything else
│   │       ├── components/  features/  lib/  styles/
│   │
│   └── api/                 NestJS modular monolith (:4000)
│       ├── prisma/          schema.prisma · migrations · seed.ts
│       └── src/
│           ├── config/          env validation → typed config object
│           ├── common/          decorators · filters · guards · interceptors
│           │                    middleware · pipes · errors
│           ├── infrastructure/  prisma · redis · storage · events · logging
│           └── modules/         20 bounded contexts
│
├── packages/
│   ├── types/           Framework-free domain + API contracts (zero deps)
│   ├── validation/      Zod schemas shared by API and both frontends
│   ├── api-client/      The only sanctioned way for a frontend to reach the API
│   ├── ui/              Design-system primitives (Button, Input, Badge, Skeleton)
│   ├── config/          Shared tsconfig bases + Tailwind theme tokens
│   └── eslint-config/   Flat configs incl. the architectural boundary rules
│
├── infrastructure/
│   ├── docker/          One Dockerfile per app (turbo prune → standalone)
│   ├── nginx/           Edge routing, rate ceiling, asset caching
│   └── scripts/         bootstrap.sh · reset-db.sh
│
├── docs/
│   ├── architecture.md  Boundaries, conventions, risks — read this first
│   └── adr/             12 decision records
│
├── docker-compose.yml   Backing services; `--profile full` runs everything
├── turbo.json  pnpm-workspace.yaml  package.json

Architecture in brief

Full detail in docs/architecture.md. The rules that matter most:

  1. Only the API touches data. Neither frontend has a database, Redis or storage client. Enforced by ESLint and by the absence of DATABASE_URL from their environments. (ADR-0004)

  2. All business logic lives in the backend. The storefront may format ₫250.000; it may never compute a discount.

  3. Product ≠ ProductVariant. A product has a page; a variant has a SKU, a price and stock. Every colour × size combination is its own variant. (ADR-0003)

  4. Authorization is permissions, never role checks. There is no if (user.role === 'ADMIN') anywhere. (ADR-0007)

  5. Authentication is on by default. The access-token guard is global; a route is public only by declaring @Public(). Forgetting a decorator fails closed.

  6. Money is an integer in minor units, everywhere. (ADR-0011)

  7. Modules own their tables exclusively. Cross-module access goes through a public/ barrel or a domain event — enforced by ESLint, which is what keeps a future service extraction possible. (ADR-0002)

Storefront routes

/ · /men · /women · /sports/[running|football|training|gym|badminton|lifestyle] · /products/[slug] · /collections/[slug] · /search · /cart · /checkout · /account/[profile|orders|addresses|wishlist] · /blog

Backend modules

auth users customers products product-variants categories collections brands inventory carts checkout orders payments promotions coupons wishlist reviews cms media search (+ health)


Roadmap

Milestone Scope
M0 ✅ Architecture, tooling, schema, health check, Docker, CI
M1 Catalog read API: products, variants, categories, collections, brands + Redis caching
M2 Auth: login, refresh rotation, RBAC admin, user/role management
M3 Admin catalog: product editor, variant matrix, media uploads, inventory
M4 Storefront catalog: listings, PDP with variant selector, filters
M5 Cart, checkout, orders
M6 Search + faceting
M7 Promotions, coupons, reviews, CMS
M8 Customer account
M9 Payments (VNPay, MoMo, ZaloPay, COD), shipping, notifications

Recommended next step: M1. It exercises every layer end to end — Prisma repository → service → controller → envelope → @sport/api-client → a rendered page — on read-only endpoints where mistakes are cheap. It also proves the variant model against real data before anything writes to it. Auth (M2) comes second because the enforcement half already exists; only issuance is missing.


Verified in this environment

Everything below was run, not assumed:

  • pnpm install — 10 workspace projects resolved
  • pnpm lint · pnpm typecheck · pnpm build — 24/24 Turborepo tasks pass
  • pnpm format:check — clean
  • prisma migrate dev — 25 tables created
  • pnpm db:seed — 36 permissions, 6 roles
  • API boots; GET /api/v1/health returns status: ok with PostgreSQL and Redis both up
  • Error envelope confirmed on a 404; x-request-id echoed; Helmet, CORS and rate-limit headers present; Swagger served at /docs
  • pnpm test — 5 passing RBAC guard tests
  • Storefront renders 20 routes (/sports/curling correctly 404s); admin renders 17

Known benign noise: NestJS logs two Unsupported route path: "/api/*" warnings at boot. They come from Nest's own global-prefix handling under Express 5 / path-to-regexp v8, are auto-converted correctly, and routing is verified working. Nothing in this repository registers that path.


Contributing

  • Branches: feat/…, fix/…, chore/…, docs/…
  • Commits: Conventional Commits
  • CI runs lint, typecheck, format, tests against real PostgreSQL and Redis, builds every app, and builds all three Docker images on push.
  • Architectural changes need an ADR in docs/adr/.
S
Description
Basic web for student project
Readme
1.2 MiB
Languages
TypeScript 93.4%
Python 4.4%
CSS 0.7%
JavaScript 0.7%
Dockerfile 0.4%
Other 0.4%