Tech stack
Turborepo monorepo, NestJS API, Next.js storefront, Vite admin, chrome-devtools MCP scraper.
TL;DR
Turborepo monorepo with three apps: API (NestJS + TypeORM + PostgreSQL 16 + Redis + Socket.IO), public storefront (Next.js 14 + next-intl + TanStack Query), admin (Vite 6 + React 18 + Leaflet). Catalog scraping — chrome-devtools MCP instead of Puppeteer. Hosting — a dedicated ARM64 VPS.
Stack and architecture
Monorepo
Turborepo builds three apps and shared packages:
auto-parts/
├── apps/
│ ├── api/ NestJS — REST + WebSocket
│ ├── web/ Next.js — public storefront
│ └── admin/ Vite + React SPA — internal admin
├── packages/
│ ├── shared/ shared DTO types
│ └── ui/ shared React components (for admin)
├── scraper/ chrome-devtools MCP scraper
└── deploy/ docker-compose, nginx configs
Turborepo gives build caching (CI rebuilds only changed apps); turbo dev runs all three in parallel for local dev.
API (NestJS)
NestJS 10 with TypeORM:
- Auth:
@nestjs/jwt+@nestjs/passport+ bcrypt. Cookie-based for admin, Bearer tokens for future integrations. - Storage: PostgreSQL 16 via TypeORM. Main entities —
Product,Category,Fitment,Order,OrderItem,Customer,DeliveryZone,Inventory,Supplier. - Cache: Redis via
@nestjs/cache-manager+@keyv/redis. Cached: filtered catalog, fitment tables, geo-bindings. - Real-time:
@nestjs/platform-socket.iowith@socket.io/redis-adapter— horizontal scaling, stock sync between admin and storefront without reload. - Docs:
@nestjs/swaggergenerates the OpenAPI spec at /api/docs. - Defence:
@nestjs/throttlerfor rate-limiting (especially on search endpoints), global guards for JWT and role checks.
Storefront (Next.js)
Next.js 14 with App Router. Key modules:
- next-intl — multilingual (LT / RU / EN). Each language is its own URL prefix (
/lt,/ru,/en), statically rendered for SEO. - TanStack Query (React Query) — server cache for catalog, cart, order statuses. Refetch on window focus, refresh via WebSocket invalidation.
- Framer Motion — transition animations between states (card loading, add-to-cart).
- Zustand — client state for cart and catalog filters.
- Tailwind CSS v4 — styling, no separate design system; components are written in place.
The catalog renders in mixed mode: category lists — statically (ISR), product cards — server-side with invalidation by timer or admin event.
Admin (Vite SPA)
Vite 6 + React 18. A heavy SPA — many screens, frequent re-renders, so the Vite dev mode gives fast hot-reload without lag, unlike a Next.js dev build.
- Leaflet — Lithuanian maps for delivery zones. Used without external APIs (OpenStreetMap tiles).
- jsPDF + jspdf-autotable — export delivery notes and reports right in the browser, no server rendering.
- react-quill-new — rich-text for product descriptions (HTML with image support).
- rrweb-player — embedded player for session replay from client complaints.
- Recharts — analytics charts (sales, conversion).
- react-router-dom 7 — client routing; file-based not used.
- Zustand — shared store, no Redux.
Auth — shared with the API via JWT + cookie on the admin-* subdomain (or basic-auth for very locked endpoints).
Scraper
A separate scraper/ folder with import scripts. The approach — chrome-devtools MCP, not Puppeteer or Playwright:
scraper/
├── README.md
├── scripts/
│ ├── import-mototex.ts
│ ├── import-anjese.ts
│ └── ...
└── data/ intermediate-result cache
MCP (Model Context Protocol) launches a real browser and drives it from a Node process. From the source site's perspective — this is an ordinary Chrome user, so it works on sites with aggressive anti-bot defences.
Import cycle: open the catalog list → walk pages → on each card pull SKU, prices, photos, fitment table → normalise → send to the API → API creates or updates the product. Progress flies into the admin over WebSocket, the operator sees the import in real time.
Hosting
- ARM64 VPS. Dedicated server, user
parts. ARM is chosen for cost and lower power draw — for a catalog without heavy compute it's non-critical. - nginx — reverse-proxy, TLS terminator, static. Virtual hosts for the public storefront and admin on different subdomains.
- Docker compose — all services (api, web, admin, postgres, redis) in one file. Deploy —
docker compose pull && docker compose up -d. - GitHub Actions — CI: lint, type-check, build → push image to GHCR. Smart path filter rebuilds only the changed apps.
Crypto payments
The API has bitcoinjs-lib and bip32 wired in — infrastructure for accepting crypto payments (BTC, ETH via derived keys). BIP32 address derivation: one master key per platform, derived addresses for each order — this gives incoming-transaction tracking without exposing the master key.
It's optional on the storefront for now, but the architecture is there; for the Russian-speaking segment in Lithuania it could become a settlement channel when euro acquiring isn't convenient.
What I'd rewrite
On a fresh iteration:
- Price attestation. Today prices are imported as-is. A normalisation layer with history could be added: "this part used to cost Y at supplier X, now Z — 30% delta, isn't this counterfeit?"
- CDN for photos. Images currently go through nginx from disk. As the catalog grows — worth switching to S3/R2 + CDN.
- Full-text search. TypeORM
LIKEhandles 10,000 SKUs, but at 50,000+ you need Postgres FTS or Meilisearch.
For now the platform runs in production, orders flow, catalog import is automated. Further iterations depend on assortment and traffic growth.