Bilingual marketing site with real backend features: an encrypted auth handshake, a self-hosted admin CMS with scheduled publishing and email campaigns, polyglot persistence, realtime comments, and a full Docker + CI/CD pipeline. The public surface is the product.
Designer & engineer
- Next.js
- NestJS
- Prisma
- Postgres
- MongoDB
- Redis
- Socket.IO
- Docker
- Turborepo
- 01Encrypted handshake: ECDH P-256 → HKDF → AES-256-GCM, forward secrecy + replay protection
- 02One CQRS-shaped API contract: typed response envelope + a full error-code catalog
- 03Self-hosted admin CMS: scheduled publishing, bulk email, ratings, analytics drill-down
- 04Polyglot persistence (Postgres · MongoDB · Redis) + realtime Socket.IO comments
- 05Monorepo, multi-stage Docker, GitHub Actions gates, Lighthouse ≥ 95
Auth payloads are encrypted end to end
Signup and login go through an application-layer envelope on top of TLS: ephemeral ECDH P-256 → HKDF-SHA256 → AES-256-GCM, with timestamp + nonce replay protection bound into the GCM AAD. The crypto SDK lives in a shared package, so client and server never drift.
- 01Client generates an ephemeral ECDH P-256 key pair
- 02ECDH(client_eph, server_pub) → HKDF-SHA256 → AES-256-GCM key
- 03Encrypt payload; bind ts + nonce into the GCM AAD
- 04Server checks ts window + unused nonce (Redis), derives the same key, decrypts
- 05Server encrypts the response under the same key — forward secrecy per request
- 06Shared crypto SDK in packages/, imported by both web and api — no client/server drift
Sessions are hardened, not just issued
Beyond the handshake, the auth core does refresh-token rotation with family reuse detection and optional TOTP MFA — the kind of hardening usually skipped on a portfolio site.
- 01Refresh tokens rotate on every use; each belongs to a session family
- 02Reuse of a retired token kills the whole family in one transaction (theft response)
- 03Optional TOTP MFA (authenticator app) with audit-logged enable/disable
- 04Every public endpoint is rate-limited; auth + subscribe carry Cloudflare Turnstile
- 05No token, key, nonce, or password hash ever reaches logs or the client bundle
One API contract, CQRS-shaped
Every endpoint speaks one envelope, enforced by a global interceptor + exception filter — not hand-written per controller. Reads return data; writes return success + message; lists carry pagination; errors carry a stable code from a single catalog.
- 01Query → { data } · Mutation → { success, message, data }
- 02Paginated → { success, data, pagination: { page, pageSize, totalItems, totalPages } }
- 03Error → { success: false, message, error: { code } } from one ErrorCode catalog
- 04Validation → { success: false, errors: [{ field, message }] }
- 05The encrypted route nests its own interceptor so the global envelope wraps the sealed blob
- 06Prisma P2025/P2002 mapped to 404/409; /health passes through untouched (Terminus format)
The site runs its own admin CMS
An allow-listed admin app manages users, posts, and subscribers as searchable paginated tables, schedules posts to publish by the minute, and sends bulk email campaigns — backed by the same hardened API.
- 01Users · Posts · Subscribers tables: offset pagination + search, behind an AdminGuard
- 02Scheduled publishing: @nestjs/schedule cron + atomic claim (multi-instance safe)
- 03Bulk campaigns: background send via Resend, fire-and-forget, bootstrap reconciliation
- 04Stateless unsubscribe: HMAC token id.HMAC("unsub:"+id) — no per-send DB write
- 05Blog ratings (1–5, one per user) + per-user activity (comments, reads, ratings)
The right store for each job
PostgreSQL holds the transactional core; MongoDB holds append-heavy and document data; Redis holds nonces and rate-limit counters. One writer per concern — no entity is dual-written.
- 01Postgres (Prisma): users · refresh tokens (family + rotation) · posts · subscribers · ratings
- 02MongoDB (Mongoose): analytics_events · contact_messages
- 03Redis (ioredis): handshake nonces + per-endpoint rate-limit windows
- 04Free-tier discipline: pooled connections, lean documents, indexes on hot paths
Realtime comments and honest read counts
Comments stream over Socket.IO so threads update without a refresh. Read counts respect ISR: instead of incrementing on a cached server render, a client beacon counts one read per session.
- 01Socket.IO gateway broadcasts new comments to everyone viewing a post
- 02Comment list is keyset-paginated; the realtime feed merges without duplicates
- 03ISR (revalidate 300s) makes server-side counting wrong → client read-beacon instead
- 04sessionStorage dedupe = one read per visitor per post; the POST /view route is throttled
Built and shipped like production
Clean/hexagonal NestJS, structured logging, health probes, and a full container + CI/CD pipeline — documented in ADRs, not improvised.
- 01Architecture: NestJS clean/hexagonal · SOLID · DI · shared types in packages/
- 02Observability: nestjs-pino structured logs · @nestjs/terminus health (Postgres + Mongo)
- 03Delivery: multi-stage Docker per app · GitHub Actions CI gates
- 04Quality: Lighthouse ≥ 95 · typed end to end · EN/VI metadata, hreflang, JSON-LD
≥95
Lighthouse target3
data stores, one writer each0
plaintext credentials on the wire