--- type: architecture title: System Overview description: High-level layout of the HIPCTF monorepo (NestJS API + Angular SPA) and how they communicate. tags: [architecture, backend, frontend, overview] timestamp: 2026-07-21T15:05:00Z --- # Overview HIPCTF is delivered as a Node.js npm workspace containing two packages: | Package | Path | Stack | |-----------|-------------|----------------------------------| | Backend | `backend/` | NestJS 10 + TypeORM + better-sqlite3 | | Frontend | `frontend/` | Angular 17 standalone components | The backend also serves the built Angular SPA as static files and falls back to `index.html` for client-side routes, so a single Node process hosts the whole application. # Topology ``` Browser ──HTTPS──▶ NestJS process (PORT, default 3000) │ ├── /api/v1/* ── controllers → services → TypeORM │ │ │ ▼ │ better-sqlite3 │ (DATABASE_PATH) │ ├── /api/docs, /api/docs-json ── Swagger UI (OpenAPI 3.1) │ ├── /uploads/* ── static file server (UPLOAD_DIR) │ └── /* ── SPA fallback (FRONTEND_DIST/index.html) ``` # Backend entrypoint flow `backend/src/main.ts` orchestrates startup in this order: 1. Create the Nest application. 2. Read configuration via `ConfigService` (validated against `backend/src/config/env.schema.ts`). 3. Call `DatabaseInitService.init()` so migrations + WAL PRAGMA run before HTTP traffic is accepted. 4. Register global middleware: `helmet`, `cookieParser`, JSON / URL-encoded parsers, then `CsrfMiddleware` (registered explicitly so it can read the parsed body). 5. Mount `/uploads` static and Angular static (`FRONTEND_DIST`). 6. Build the OpenAPI 3.1 document via `toOpenApi31()` and expose Swagger UI at `/api/docs` and JSON at `/api/docs-json`. 7. Install `SpaFallbackMiddleware` so non-`/api`, non-`/uploads`, extensionless `GET` requests return `index.html`. 8. `app.listen(port)`. # Frontend entrypoint flow `frontend/src/main.ts` bootstraps a standalone Angular app: 1. `provideHttpClient(withInterceptors([csrfInterceptor, authInterceptor]))` attaches the CSRF header on unsafe methods and the Bearer access token on every request. 2. `provideRouter(APP_ROUTES, withComponentInputBinding())`. 3. `AppComponent.ngOnInit()` calls `BootstrapService.load()` then `AuthService.restoreSession()`. Bootstrap fetches `GET /api/v1/bootstrap` and applies the returned theme tokens to CSS custom properties on `document.documentElement`. Session restore rehydrates the in-memory auth signals from `sessionStorage` and posts `POST /api/v1/auth/refresh` with `withCredentials: true` so a hard refresh does not log the user out. 4. Guards (`authGuard`, `adminGuard`) are async and `await` `BootstrapService.ready()` + `AuthService.waitUntilHydrated()` before reading state, so refreshing on a deep link never flashes `/bootstrap` or `/login`. # Cross-cutting concepts | Concern | Backend | Frontend | |----------------|------------------------------------------------------|---------------------------------------------| | Authentication | `JwtAuthGuard` (global) + `AuthGuard('jwt')` strategy | `AuthService` signal + `authInterceptor`; session is mirrored to `sessionStorage` and rehydrated on boot via `/api/v1/auth/refresh` | | Authorization | `AdminGuard` + `@Roles('admin')` decorator | `authGuard` for `/`, `adminGuard` for `/admin` (both async, both delegate to pure decision functions: `decideAuthRedirect` / `decideAdminGuard`) | | CSRF | `CsrfMiddleware` (skips `/api/v1/auth/login` and `/api/v1/auth/register-first-admin`) | `csrfInterceptor` reads `csrf` cookie | | Errors | `ApiError` → `GlobalExceptionFilter` returns `{code, message, details, path, timestamp}` | Components display `error?.error?.message` | | Config | `envSchema` (zod) + `validateEnv` | None (consumed via bootstrap) | # See also - [Backend Module Map](/architecture/backend-modules.md) - [Frontend Structure](/architecture/frontend-structure.md) - [REST API Overview](/api/rest-overview.md)