This repository is a reference architecture showcase extracted from a real, production system. It focuses on architectural decisions, boundaries, and operational patterns — not on business-specific logic or a ready-to-deploy application.
The full system context, constraints, and outcomes are described in the corresponding case study: 👉 https://rocketdeploy.dev/en/case-studies/internal-operational-spa/
This is a neutral, reference single‑page application (SPA) built to demonstrate a production‑grade frontend architecture for authenticated, role‑based workflows. It owns client‑side routing, authentication handoffs, UI composition, view‑level state, localization, and cross‑cutting concerns like theming and error handling. The problems it solves are architectural: how to structure a modular UI, how to gate access, how to attach auth tokens safely, and how to keep user experience consistent across feature areas. It intentionally does not cover backend contracts, domain workflows, business rules, or any brand‑specific product behavior.
- Language: TypeScript — chosen for strict typing and refactor safety in a large UI surface, keeping component contracts explicit and reducing runtime defects.
- Framework: Angular (standalone APIs) — selected for its structured dependency injection, first‑class routing, and predictable component lifecycle, enabling clear architectural boundaries without heavy module scaffolding.
- Reactive model — Angular Signals are used for synchronous UI state because they are lightweight and deterministic; RxJS is used for asynchronous streams (HTTP, interceptors, loaders) because it provides cancellation, composition, and error propagation.
- Application bootstrap & dependency wiring — establishes the global providers (routing, HTTP pipeline, i18n, animations, error listeners) that define system‑wide behavior; it exists to centralize cross‑cutting concerns and keep features lean.
- Routing & access control model — defines the public and protected navigation surfaces and the rules that govern entry; it exists to enforce consistent access boundaries before any feature is mounted.
- Authentication boundary & session lifecycle — describes how the SPA initializes a session with an external identity provider and tracks authenticated state; it exists to keep session logic centralized and reusable.
- HTTP client pipeline — outlines how outbound requests flow through token attachment and error handling; it exists to keep security and retry behavior consistent across all API calls.
- Feature area composition — explains how UI domains are partitioned into feature areas that own view composition and localized state; it exists to reduce coupling and allow parallel development.
- Cross‑cutting services — covers global concerns such as theme preference and localization; it exists to provide stable, shared state without embedding it in individual features.
- Application layout shell — describes the shared structure around protected areas (navigation, framing); it exists to keep layout responsibilities outside feature components.
- Observability & UX feedback — describes how the UI surfaces loading, empty, and error states; it exists to make system behavior explicit to operators without exposing backend details.
[Browser]
|
v
[App Shell]
|
+--> [Router + Guards]
| |
| v
| [Feature Areas]
|
+--> [Cross‑cutting Services]
| |-- Auth Service
| |-- Theme Service
| |-- I18n Loader
|
+--> [HTTP Pipeline]
|-- Auth Header Interceptor
|-- Auth Error Retry
v
[Same‑origin API]
Layer responsibilities & boundaries
- App Shell: bootstraps global providers, error listeners, and application‑wide configuration.
- Router + Guards: defines navigation structure and enforces access control before features load.
- Feature Areas: own view composition and view‑level state; they should not contain global wiring.
- Cross‑cutting Services: encapsulate session, theme, and localization concerns used across features.
- HTTP Pipeline: ensures tokens are attached only when appropriate and handles auth failures once.
- Feature isolation: feature areas should not import each other directly; they interact via routing and shared services.
- Auth boundary: only the auth area should touch the external identity provider; other modules consume the auth service interface.
- HTTP boundary: token attachment and retry logic live exclusively in interceptors; components do not manually manage headers.
- UI state boundary: global UI state (theme, locale) is owned by dedicated services and exposed via signals.
- Enforcement: the boundaries are enforced by module structure, provider composition, and route‑level guard placement.
- Accepted coupling: routing definitions reference concrete feature components to keep navigation explicit and predictable.
- Application shell: app bootstrap, provider wiring, root layout, and global styles.
- Routing: route tree, access‑denied handling, and authenticated layout composition.
- Authentication: session lifecycle, token refresh, group‑based access, and HTTP interception.
- Internationalization: translation manifest loading and fallback resolution.
- Theming: preference resolution, persistence, and DOM theme application.
- Feature areas: discrete UI domains containing page‑level components and templates.
- Integration model: REST over HTTP with JSON payloads, using same‑origin API paths (for example
/api/*). - Authentication: OAuth2 / OpenID Connect with Authorization Code flow + PKCE via an external identity provider.
- Token handling: session is maintained in memory by the auth client; tokens are attached automatically through HTTP interceptors; same‑origin checks prevent token leakage to third‑party origins.
- Error handling contract: 401 triggers a single token refresh attempt and a retry, then falls back to login; 403 routes to an access‑denied view; 5xx surfaces as UI error states.
- Separation of concerns: UI components do not embed API details; API calls are centralized in an integration layer or HTTP client abstraction.
- Idempotency & safety: retry markers prevent loops; mutating operations do not retry optimistically.
-
Initial load and session bootstrap
- Trigger: browser loads the SPA.
- Validation / guards: global initializer starts the auth client and records session state.
- Core action: providers are registered and routing becomes active.
- Side effects: error listeners are attached globally.
- Observability / UX feedback: navigation begins immediately; auth status drives redirects.
- Implementation notes: an application bootstrap sequence wires provider setup and a session initializer, guaranteeing auth state is known before protected routes are evaluated.
-
Protected navigation
- Trigger: user navigates to a protected route.
- Validation / guards: route guard checks auth enabled state and session status.
- Core action: if unauthenticated, a login redirect is initiated; if authorized, navigation proceeds.
- Side effects: access‑denied navigation if group membership fails.
- Observability / UX feedback: user is routed to an access‑denied view or to the requested area.
- Implementation notes: a guard runs before feature mounting; it depends on the auth service for session checks and enforces authorization via group membership rules.
-
Authenticated API request
- Trigger: a component issues an HTTP request to a same‑origin API path.
- Validation / guards: interceptor checks same‑origin and auth enabled state.
- Core action: token is refreshed if needed and injected into the request header.
- Side effects: request proceeds with updated authorization header.
- Observability / UX feedback: standard request/response flow; failures propagate to the caller.
- Implementation notes: a dedicated HTTP interceptor owns token attachment and refresh logic, ensuring UI components remain unaware of authentication mechanics.
-
Auth failure retry
- Trigger: an API call responds with 401 unauthorized.
- Validation / guards: interceptor ensures the request is same‑origin and not already retried.
- Core action: token refresh is attempted and a single retry is issued with a retry marker header.
- Side effects: on failure, login is initiated and error is rethrown.
- Observability / UX feedback: retry is transparent; login redirect provides recovery.
- Implementation notes: retry handling is centralized in the HTTP pipeline, and a retry marker guarantees a single attempt to prevent loops.
-
Language resolution
- Trigger: localization system requests translations for the active locale.
- Validation / guards: manifest lookup resolves the concrete translation file.
- Core action: translation file is fetched and returned to the i18n provider.
- Side effects: fallback to a legacy file if the manifest is missing or invalid.
- Observability / UX feedback: UI re‑renders on language change.
- Implementation notes: the i18n loader abstracts file resolution and fallback behavior, guaranteeing a translation response even if a manifest lookup fails.
-
Theme preference application
- Trigger: app starts or user switches theme.
- Validation / guards: service resolves stored preference or system preference.
- Core action: theme signal updates DOM classes and meta theme color.
- Side effects: preference is persisted to local storage.
- Observability / UX feedback: immediate visual change.
- Implementation notes: a global theme service owns state and persistence, ensuring a single source of truth for UI theming.
- Where state lives: primarily in component state and lightweight services (e.g., auth and theme).
- How it mutates: local mutations via Angular signals and service methods; async flows coordinated with RxJS in interceptors and loaders.
- Consistency guarantees: auth and theme state are singletons provided at the root; token refresh updates are serialized by the auth client.
- Caching/invalidation: minimal caching; auth token validity is refreshed on demand; i18n loader relies on manifest resolution and HTTP caching.
- What the frontend does not handle: domain‑specific data modeling, server‑side validation, or business rules beyond access checks.
- Loading/empty/error states: managed at the component level rather than via a centralized UI state store.
- Retry/recovery: a single automatic retry on 401 responses, followed by re‑authentication if needed.
- Idempotency/guards: retry is marked with a request header to prevent loops.
- Fallback behavior: i18n loader falls back to legacy translation files; theme defaults to system preference.
- Authentication assumptions: relies on an external identity provider and client‑side session initialization.
- Authorization boundaries: route guard enforces membership‑based access before loading protected routes.
- Sensitive data handling: auth tokens are kept in memory via the auth client and added only to same‑origin API calls.
- Validation responsibility split: the frontend enforces navigation boundaries; the server remains the final authority for data access.
- Use a provider‑driven app bootstrap — simplifies global wiring; trade‑off: harder to unit test provider order; risk: mis‑ordered dependencies; mitigation: keep providers centralized and minimal.
- Route‑first feature composition — keeps navigation explicit; trade‑off: tighter coupling to component classes; risk: refactors require route updates; mitigation: keep routes in one file and enforce conventions.
- Guard‑based access control — prevents unauthorized routes from mounting; trade‑off: guard complexity; risk: guard logic can drift from backend; mitigation: keep guard checks minimal and role‑based.
- Interceptor‑only token attachment — avoids scattering auth headers; trade‑off: all HTTP must go through the client; risk: bypassed requests; mitigation: linting and code review rules.
- Single retry on 401 — reduces noisy failures; trade‑off: extra latency on expired tokens; risk: retry storms if misconfigured; mitigation: retry marker header and login fallback.
- Same‑origin API check — avoids token leakage to third‑party calls; trade‑off: limits cross‑origin integration; risk: missed API calls; mitigation: define proxy or same‑origin paths.
- Signal‑based UI state — simple and synchronous for small global state; trade‑off: not a full state store; risk: state sprawl; mitigation: keep signal usage confined to dedicated services.
- Manifest‑driven i18n loading — enables locale file indirection; trade‑off: extra HTTP request; risk: missing manifest entries; mitigation: legacy fallback loader.
- Layout shell for protected areas — consistent navigation and guard scoping; trade‑off: nested routing complexity; risk: hard to reason about redirects; mitigation: keep default redirect simple.
- Minimal global error handling — uses browser global error listeners; trade‑off: limited UI‑level recovery; risk: unhandled errors surface in console; mitigation: component‑level error boundaries where needed.
- Local storage for theme preference — persistence across sessions; trade‑off: client‑side storage reliance; risk: storage unavailable; mitigation: fallback to system preference.
- No shared domain store — reduces complexity; trade‑off: duplicated fetching logic; risk: inconsistent UI states; mitigation: isolate data handling per feature and add shared utilities if scale requires.
- Outside SPA constraints: prefer server‑side rendered shell for faster first paint; trade‑off: increased infrastructure complexity; risk: divergence between SSR and SPA; mitigation: shared rendering contracts.
- Outside SPA constraints: adopt edge‑cached translation bundles; trade‑off: more build tooling; risk: stale language files; mitigation: cache busting via manifest versioning.
- Where to start: build a mental model from the bootstrap sequence, route boundaries, and HTTP pipeline.
- What to look for: consistency of boundaries (auth, HTTP, i18n, theming), and how feature areas avoid leaking cross‑cutting concerns.
- What is intentionally abstracted: domain logic, API contracts, and any brand‑specific workflow details.
This document is an anonymized technical reference intended for architectural discussion. It is not a production‑ready specification, and it focuses strictly on frontend structure rather than product behavior.
If you’re dealing with a similar problem — building role-based operational frontends, designing secure frontend-to-backend integration patterns, or scaling an SPA beyond MVP stage — feel free to reach out: