Developing

Architecture

rollnote is two applications plus a database. This is the contributor’s-eye view; each repository carries a deeper docs/ARCHITECTURE.md with the full detail.

The pieces

ComponentStack
rollnote-apiNestJS 11 (Express), TypeScript, PostgreSQL 18, Drizzle ORM, Better-Auth
rollnote-frontAngular 21 (standalone components, signals), PrimeNG, Better-Auth client
rollnote-websiteSvelteKit (this site)

The frontend is a pure client of the API: all persistence is server-side and authentication is by cookie.

API: one module per domain

The API is organised as vertical slices - one NestJS module per domain (shoot, frame, camera, lens, film, and so on). A module is a controller -> service -> Drizzle schema chain, and a service only touches its own tables. Cross-domain reads go through the owning service, never another module’s tables directly.

Every request passes through the same pipeline:

  1. Request ID middleware tags the request with an X-Request-Id.
  2. Throttler enforces rate limits (tighter limits on auth routes).
  3. Better-Auth guard rejects unauthenticated requests unless the handler is marked public.
  4. Validation pipe whitelists and type-coerces the request body against its DTO, rejecting unknown fields.
  5. The controller and service do the work via Drizzle.
  6. A foreign-key exception filter turns Postgres constraint violations into friendly 409 / 400 responses.

Data model

All identifiers are UUID v7. The core shape:

user ─┬─ shoot ──< frame ──── frame_location (1:1)
      ├─ camera, lens, filter, film, iso, film_format, weather
      └─ folder

Reference data such as film stocks, ISO values, formats, and weather can be global (seeded defaults visible to everyone) or user-created. The seed runs on every boot and is idempotent.

Security

  • Encryption at rest: sensitive user columns are encrypted with ENCRYPTION_KEY. Email is encrypted deterministically so it stays unique and searchable.
  • Helmet sets security headers; CORS allows only the configured FRONT_URL, with credentials.
  • Auth needs the raw request stream, so Nest’s body parser is disabled globally and size limits are configured inside the auth module.

Frontend

The Angular app is signal-first with OnPush change detection throughout. Routed features live under src/app/*; shared, non-routed code under src/lib/*; route guards under src/guards/*. A CrudBaseService backs every equipment and domain service, and a global error interceptor maps 401 to the login page and surfaces other failures as toasts. In production it is a PWA, served as a static bundle by nginx.

Going deeper

  • rollnote-api/docs/ARCHITECTURE.md - request lifecycle, the created_by casing quirk, migrations, and testing in detail.
  • rollnote-front/docs/ARCHITECTURE.md - routing, the auth/session bridge, and API integration.