Reference repo: namastedev/namaste-frontend-system-design
Note: Performance Optimization and Testing are covered in depth in separate domain tables. Sections here focus on system design–level thinking — not implementation details.
| Topic | Core Concepts & Mental Model | Tools & Libraries | Key Techniques | Tradeoffs & Failure Modes | Resources |
|---|---|---|---|---|---|
| REST | Resources identified by URLs, stateless, HTTP verbs map to CRUD. Simplest API contract — universally understood. Cacheable by nature (GET) | Postman, Thunder Client, Swagger/OpenAPI, Axios, fetch | Proper verb usage (GET/POST/PUT/PATCH/DELETE), status codes (2xx/4xx/5xx), versioning (/api/v1/), pagination, filtering via query params |
✅ Simple, universal, CDN-cacheable GETs ❌ Over-fetching (getting fields you don't need), under-fetching (N+1 trips for related data), no strict type contract between client/server | RESTful API Design, HTTP Status Codes |
| GraphQL | Single endpoint, client declares exact data shape needed. Schema is a typed contract shared by client and server. Eliminates over/under-fetching | Apollo Client, URQL, GraphQL Playground, Apollo Studio | Queries (read), mutations (write), subscriptions (real-time), fragments for reuse, DataLoader (server-side) to solve N+1, persisted queries |
✅ Client controls shape, great for nested/relational UIs, self-documenting schema ❌ N+1 query problem server-side if not using DataLoader; caching harder than REST (non-GET); complexity overhead for simple CRUD; large queries can be expensive | GraphQL.org, Apollo Client docs |
| gRPC | Binary protocol using Protocol Buffers. Strongly typed IDL-defined contracts, streaming support. Not natively browser-compatible — requires gRPC-Web proxy or Envoy sidecar on frontend | gRPC-Web, Protobuf, Envoy proxy | Unary, server/client/bidirectional streaming, .proto schema as single source of truth for types, code generation for clients |
✅ Extremely fast (binary, compressed), strong typing via Protobuf, native streaming ❌ Not usable directly in browser without gRPC-Web + proxy layer; harder to debug than REST (binary not human-readable); limited browser support; overkill for most web apps | gRPC-Web, Protobuf docs |
| tRPC | Type-safe RPC — call server functions from client with full TypeScript type inference. No schema, no codegen, no REST URLs. Types flow end-to-end automatically | tRPC v11+, Zod (input validation), TanStack Query (transport layer) | Routers + procedures (query/mutation), Zod schemas for input validation, context for auth/session, middleware on procedures, Next.js adapter | ✅ Zero type duplication, best-in-class DX for TypeScript monorepos, no API contract drift ❌ Tight client-server coupling (same TypeScript repo required); not suitable for public APIs or non-TypeScript consumers; not REST — can't be called from Postman | tRPC docs |
| Topic | Core Concepts & Mental Model | Tools & Libraries | Key Techniques | Tradeoffs & Failure Modes | Resources |
|---|---|---|---|---|---|
| Short Polling | Client asks server "any updates?" on a fixed interval. Simple to implement. Server always responds immediately (empty or with data) | setInterval + fetch, TanStack Query refetchInterval |
Fixed interval (e.g. 5s), exponential backoff on error, stop polling on component unmount/tab hidden | ✅ Works everywhere, simple ❌ Wasteful — sends requests even when nothing changed; server load grows linearly with users; not suitable for low-latency or high-frequency scenarios | — |
| Long Polling | Client sends request, server holds it open until it has data to return (or timeout). Then client immediately re-requests | Custom Node.js endpoint with delayed response | Server holds response via async timeout, client loops on response, timeout + reconnect strategy required | ✅ Lower latency than short polling, no persistent connection ❌ Server must hold many concurrent open connections; reconnect logic adds complexity; harder to scale behind load balancers | — |
| Server-Sent Events (SSE) | Persistent one-way HTTP stream: server → client. Uses standard HTTP (not a new protocol). Browser auto-reconnects on disconnect | EventSource API (native), Next.js Route Handler returning ReadableStream, @microsoft/fetch-event-source |
Named event types (event: message), data: field, id: for reconnect cursor, retry: for reconnect interval, route handler: return new Response(stream) |
✅ Simple, HTTP-only, auto-reconnect built-in, works through proxies/firewalls ❌ Server → client only; max 6 connections per domain on HTTP/1.1 (use HTTP/2); Vercel/serverless functions time out — use streaming or dedicated SSE server | MDN SSE, EventSource API |
| WebSockets | Full-duplex persistent TCP connection. Both sides send messages at any time. Requires HTTP → WS upgrade handshake | Socket.io (abstraction + fallback), native WebSocket API, Pusher/Ably/PartyKit (hosted) |
Event-based messaging, rooms/namespaces (Socket.io), auth via handshake query/cookie, heartbeat/ping-pong for connection health, reconnection with backoff | ✅ Lowest latency bidirectional communication ❌ Vercel/AWS Lambda serverless don't support persistent WS natively — use hosted service or dedicated server; reconnect + auth state management is complex; stateful connections don't scale horizontally without Redis pub/sub | Socket.io docs, MDN WebSocket |
| Webhooks | Server-to-server HTTP callbacks. External service calls your endpoint when an event occurs. Asynchronous, event-driven | ngrok (local testing), Svix (webhook platform), Express/Next.js Route Handler | Verify payload signature (HMAC X-Signature header), idempotency (process same event twice safely), respond 200 fast then process async (queue), retry handling |
✅ Decoupled, async, no polling ❌ No delivery guarantee without retry logic on sender side; your endpoint must be publicly reachable (problem in dev — use ngrok); replay attacks if signature not verified; timeouts if processing is slow | Stripe Webhook Guide |
Mental Model: Security is defence in depth — multiple layers. No single technique is sufficient. Frontend security focuses on: (1) protecting the DOM, (2) protecting the network, (3) protecting storage, (4) protecting the user's identity.
| Topic | Core Concepts & Mental Model | Tools & Libraries | Key Techniques | Tradeoffs & Failure Modes | Resources |
|---|---|---|---|---|---|
| XSS (Cross-Site Scripting) | Attacker injects malicious script via user-generated content. Runs with full origin privileges. Three types: Stored (in DB), Reflected (in URL), DOM-based (client-side JS) | DOMPurify (sanitize HTML), Content-Security-Policy header |
Sanitize before rendering user HTML (DOMPurify.sanitize()), avoid dangerouslySetInnerHTML, set strict CSP header, encode output (React JSX does this by default) |
❌ React's JSX escapes by default — only dangerous when you bypass it (dangerouslySetInnerHTML, eval, innerHTML). DOM-based XSS comes from document.write, location.href = userInput, etc. |
OWASP XSS, DOMPurify |
| CSRF (Cross-Site Request Forgery) | Attacker tricks authenticated user's browser into making requests to your server using stored cookies. Exploits the browser's automatic cookie-sending behavior | — | SameSite=Strict or SameSite=Lax on session cookies, anti-CSRF token in form/header, validate Origin/Referer header on mutations, CAPTCHA for critical actions, avoid state-changing GET requests |
❌ SameSite=Lax (browser default) doesn't protect against top-level cross-site POST. SameSite=Strict can break OAuth flows. JWT in Authorization header is CSRF-immune (not auto-sent by browser) |
OWASP CSRF |
| CORS (Cross-Origin Resource Sharing) | Browser enforces same-origin policy — blocks cross-origin requests by default. CORS headers tell the browser which origins, methods, and headers are allowed | cors npm package (Node), browser DevTools Network tab |
Configure Access-Control-Allow-Origin (never * with credentials), Access-Control-Allow-Methods, preflight (OPTIONS) handling, credentials: 'include' only when necessary |
❌ Common mistake: setting Access-Control-Allow-Origin: * with Access-Control-Allow-Credentials: true — browsers reject this. CORS is a browser protection, not a server security measure — server-to-server requests ignore it |
MDN CORS |
| Content Security Policy (CSP) | HTTP response header that tells the browser which sources are trusted for scripts, styles, images, frames, etc. Prevents XSS by refusing to execute untrusted scripts | CSP Evaluator (Google), next.config.js headers, helmet (Node) |
script-src 'self' (no inline scripts), object-src 'none', base-uri 'self', report-only mode for testing (Content-Security-Policy-Report-Only), nonce-based for inline scripts |
❌ Too strict CSP breaks third-party integrations (analytics, chat widgets); 'unsafe-inline' defeats XSS protection; start with report-only mode and tighten gradually |
MDN CSP, CSP Evaluator |
| Security Headers | HTTP response headers that instruct browsers on security policies. One of the highest-ROI security improvements — add in next.config.js or CDN config |
next.config.js headers(), helmet (Express), securityheaders.com |
X-Frame-Options: DENY (clickjacking), X-Content-Type-Options: nosniff (MIME sniffing), Strict-Transport-Security (HSTS — force HTTPS), Referrer-Policy: strict-origin-when-cross-origin, Permissions-Policy |
❌ Missing X-Frame-Options = clickjacking. Missing HSTS = protocol downgrade attack. Wrong Referrer-Policy leaks sensitive URL paths to third parties |
securityheaders.com, OWASP Secure Headers |
| Clickjacking & iframe Security | Attacker embeds your page in an invisible iframe on their site and tricks user into clicking elements | — | X-Frame-Options: DENY or SAMEORIGIN, CSP frame-ancestors 'none', sandbox attribute on iframes you embed (sandbox="allow-scripts allow-same-origin") |
❌ X-Frame-Options is deprecated in favor of CSP frame-ancestors — set both for compatibility |
MDN X-Frame-Options |
| Subresource Integrity (SRI) | Ensures external scripts/styles (from CDN) haven't been tampered with by verifying a cryptographic hash. If CDN is compromised, the browser refuses the resource | srihash.org | Add integrity="sha384-<hash>" + crossorigin="anonymous" on external <script> and <link> tags |
❌ Hash must be updated whenever the external resource version changes; doesn't protect dynamically loaded scripts (only static URLs); not needed if you self-host all assets | MDN SRI |
| Permissions Policy | HTTP response header that controls which browser features (camera, microphone, geolocation) a page or its iframes can use. Replaced Feature Policy | res.setHeader("Permissions-Policy", ...), DevTools → Application → Frames |
Permissions-Policy: geolocation=(self), camera=(), microphone=() — empty () = denied for everyone including self. (self) = allowed only for your origin |
❌ Permissions Policy only restricts browser APIs, not server-side operations. Not supported in all browsers | MDN Permissions Policy |
| Input Validation & Sanitization | Validate on both client (UX feedback) and server (security). Client-side validation is for convenience, never for trust | Zod, Yup, React Hook Form, DOMPurify | Type (string, email, number), format (regex), range (min/max), required fields. Sanitize HTML before display, never before storage (store raw, sanitize on output) |
❌ Sanitizing before storing and then sanitizing again on display = double-sanitization corruption. Client-side validation only = bypassable with DevTools/curl. Never trust Content-Type header alone |
Zod docs |
| Dependency Security | Third-party npm packages are a supply chain attack vector. Regular auditing is a production hygiene practice | npm audit, pnpm audit, Snyk, Socket.dev, Renovate (auto-updates) |
Run npm audit --audit-level=high in CI, use package-lock.json / pnpm-lock.yaml (lockfiles prevent version drift), pin major versions of critical packages |
❌ npm install without lockfile = gets latest (possibly compromised) version; node_modules is 3rd-party code running with full privileges; typosquatting (e.g. reakt instead of react) |
Socket.dev, npm audit docs |
| HTTPS & Transport Security | All production traffic must be HTTPS. Mixed content (HTTP resource on HTTPS page) is blocked by browsers. HSTS prevents downgrade attacks | Vercel (auto HTTPS), Let's Encrypt, Cloudflare | Enable HSTS (Strict-Transport-Security: max-age=31536000; includeSubDomains), redirect HTTP→HTTPS at CDN/server level, fix mixed content warnings, Secure flag on cookies |
❌ Including subdomains in HSTS is permanent — misconfigured subdomain bricks it. Never preload HSTS without testing thoroughly | MDN HSTS |
| SSRF Awareness (System Design) | Server-Side Request Forgery — attacker causes your backend to fetch an unintended internal/external URL (e.g., AWS metadata endpoint 169.254.169.254). Frontend devs need to understand this for system design reviews |
Burp Suite, OWASP ZAP | Frontend design responsibility: never send raw user-provided URLs to server-side fetch without validation; backend must whitelist allowed domains/protocols | ❌ A frontend input field that sends a user-provided URL to a server-side fetch (e.g., "paste any URL to preview") is a classic SSRF vector | OWASP SSRF |
| Storage Security | Different storage mechanisms have different security characteristics. Choosing the wrong one exposes tokens to XSS or CSRF | Chrome DevTools → Application tab | HttpOnly + Secure cookies (not readable by JS — XSS-safe) for session tokens. Avoid storing JWTs in localStorage (readable by JS — XSS-vulnerable). sessionStorage for ephemeral state |
❌ Most common mistake: storing JWT access tokens in localStorage for "convenience" — any XSS steals all tokens permanently. Use HttpOnly cookie + short-lived access token instead |
OWASP Session Management |
| Topic | Core Concepts & Mental Model | Tools & Libraries | Key Techniques | Tradeoffs & Failure Modes | Resources |
|---|---|---|---|---|---|
localStorage |
Key-value string storage. Persists across browser sessions and tabs. Synchronous (blocks main thread for large data) | localStorage API |
Store non-sensitive user preferences (theme, language). Always serialize with JSON.stringify/parse. Check storage quota (~5MB) |
❌ Synchronous reads block main thread; accessible to all JS on the page (XSS risk); no expiry mechanism — you must implement TTL manually; not suitable for sensitive data | MDN localStorage |
sessionStorage |
Same API as localStorage but data is cleared when the tab closes. Isolated per tab |
sessionStorage API |
Ephemeral state that should not persist: wizard form progress, temp search filters, one-time tokens | ❌ Not shared across tabs — cannot use for cross-tab communication; lost on refresh in some cases; same XSS risk as localStorage | MDN sessionStorage |
| Cookies | Small key-value strings sent with every HTTP request automatically by the browser. The only storage type that participates in HTTP | document.cookie (avoid), js-cookie library, server-set Set-Cookie header |
HttpOnly (not readable by JS), Secure (HTTPS only), SameSite=Strict/Lax, Expires/Max-Age, Path scoping. Set from server for sensitive cookies |
❌ Sent with every request (bandwidth cost); 4KB size limit; document.cookie API is painful to use raw; third-party cookies being phased out |
MDN Set-Cookie |
| IndexedDB | Asynchronous NoSQL database in the browser. No size limit (beyond disk). Supports indexes, transactions, and structured data (not just strings) | IndexedDB API (raw), Dexie.js (Promise-based wrapper) |
Use for large datasets (offline data, cached API responses, file blobs). Dexie simplifies schema migration, versioning, and querying | ❌ Raw API is verbose and complex — always use Dexie or similar wrapper; no automatic sync with server; schema migrations require version bumps | MDN IndexedDB, Dexie.js |
| HTTP Caching | Browser and CDN caching controlled by response headers. Multi-level: memory cache → disk cache → service worker → CDN → origin | Chrome DevTools Network tab (check from cache) |
Cache-Control (controls who caches and how long), ETag + If-None-Match (conditional validation requests — 304 Not Modified), Last-Modified + If-Modified-Since, s-maxage for CDN vs browser |
❌ Priority order: Cache-Control > Expires > heuristic. Stale CDN cache is the #1 "works in dev, broken in prod" bug. no-cache ≠ "don't cache" — it means "revalidate before using cache" |
MDN Cache-Control, web.dev HTTP cache |
| Service Worker | A JavaScript proxy that runs in a background thread, intercepts all network requests, and decides: serve from cache or go to network. Required for PWA and offline | Workbox (abstracts SW caching strategies), sw.js (custom) |
Lifecycle: install (precache) → activate (clean old caches) → fetch (intercept). Strategies: Cache-first, Network-first, Stale-while-revalidate, Network-only, Cache-only |
❌ Requires HTTPS (except localhost); bugs in SW can hard-brick your app (bad cache = stuck forever); tricky to update (old SW serves old cache until all tabs close); test with DevTools → Application → Service Workers |
MDN Service Worker, Workbox docs |
| API Caching (Client-side) | Cache server responses in memory on the client to avoid redundant network requests. Different from HTTP caching — this is application-level | TanStack Query, SWR, Apollo Client | staleTime (how long data is considered fresh), cacheTime/gcTime (how long to keep in memory), refetchOnWindowFocus, invalidateQueries on mutation, Apollo fetch-policy |
❌ Stale cache after mutation = UI shows old data; cache key collisions from non-unique query keys; memory leak if cache is never garbage-collected | TanStack Query caching |
| Data Normalization | Store entities flat (like a relational DB) — indexed by ID, no nesting. Prevents duplication and sync bugs across components | Redux Toolkit createEntityAdapter, custom normalized store |
entities: { users: {1: {...}, 2: {...}}, posts: {...} } + ids: [1, 2]. Reference by ID, not by embedding |
❌ Requires upfront schema design; denormalization needed at read time (selectors); unfamiliar to devs used to raw API response shapes | Redux normalization guide |