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
| Component | Stack |
|---|---|
rollnote-api | NestJS 11 (Express), TypeScript, PostgreSQL 18, Drizzle ORM, Better-Auth |
rollnote-front | Angular 21 (standalone components, signals), PrimeNG, Better-Auth client |
rollnote-website | SvelteKit (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:
- Request ID middleware tags the request with an
X-Request-Id. - Throttler enforces rate limits (tighter limits on auth routes).
- Better-Auth guard rejects unauthenticated requests unless the handler is marked public.
- Validation pipe whitelists and type-coerces the request body against its DTO, rejecting unknown fields.
- The controller and service do the work via Drizzle.
- A foreign-key exception filter turns Postgres constraint violations into
friendly
409/400responses.
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, thecreated_bycasing quirk, migrations, and testing in detail.rollnote-front/docs/ARCHITECTURE.md- routing, the auth/session bridge, and API integration.