- TypeScript 98%
- PLpgSQL 0.9%
- JavaScript 0.7%
- Dockerfile 0.4%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
|
|
||
| .forgejo/workflows | ||
| drizzle | ||
| src | ||
| test | ||
| .dockerignore | ||
| .env.example | ||
| .fallowrc.json | ||
| .gitignore | ||
| .gitleaksignore | ||
| .nvmrc | ||
| .pnpmfile.cjs | ||
| .prettierrc | ||
| CLAUDE.md | ||
| compose.yml | ||
| dev.compose.yml | ||
| Dockerfile | ||
| drizzle.config.ts | ||
| eslint.config.mjs | ||
| jest.int.config.cjs | ||
| jest.unit.config.cjs | ||
| LICENSE | ||
| nest-cli.json | ||
| openapi.json | ||
| package.json | ||
| pnpm-lock.yaml | ||
| pnpm-workspace.yaml | ||
| README.md | ||
| tsconfig.build.json | ||
| tsconfig.json | ||
rollnote-api
NestJS REST API powering the rollnote film-photography journal: it manages users, film stocks, shoots, frames, lenses, ISOs, and weather. Data lives in PostgreSQL via Drizzle ORM. Auth is handled by Better-Auth.
Prerequisites
- Node.js 24+ (
.nvmrc;enginesis enforced) - pnpm (version pinned by
packageManager, use corepack) - Docker or Podman (for the dev Postgres stack and the integration tests)
Local dev
pnpm install
cp .env.example .env.local # POSTGRES_USER / POSTGRES_PASSWORD / POSTGRES_DB for the dev DB
docker compose -f dev.compose.yml up -d # Postgres on 127.0.0.1:5432 (reads .env.local)
cp .env.example .env # app config, loaded by dotenv at boot
pnpm start:dev # watch mode on :3000
Two env files, two consumers: dev.compose.yml reads .env.local for the
Postgres container, and the app itself loads .env (via dotenv/config).
When the app runs on the host, point DATABASE_URL in .env at
localhost:5432. On boot the app applies pending migrations and runs the
idempotent seed, so a fresh database initializes itself.
The Swagger UI is mounted at /api whenever NODE_ENV !== production.
OpenAPI document
openapi.json at the repo root describes every route except Better-Auth's
/api/auth/*. Regenerate it after changing a route or a response shape and
commit it:
pnpm openapi:generate # nest build, then writes openapi.json; needs no database
CI runs the same command and fails if the committed file differs.
Environment
Required variables are listed in .env.example. The schema lives in
src/config/env.schema.ts and is validated at boot - startup fails loudly
if anything required is missing or malformed. ENCRYPTION_KEY is a
base64-encoded 32-byte key (openssl rand -base64 32); SMTP_* and
EMAIL_FROM are only required when NODE_ENV=production.
Database
Migrations are managed by drizzle-kit:
pnpm drizzle-kit generate # diff schema -> new SQL file under drizzle/
pnpm drizzle-kit migrate # apply pending migrations
The app also runs migrate(...) on boot, so a fresh container will catch
itself up automatically.
Tests, lint, format
pnpm test # unit tests (fast, no external deps)
pnpm test:int # integration tests (boots throwaway Postgres + Mailpit)
pnpm test:all # unit + integration
pnpm lint # eslint, check only (CI gate)
pnpm lint:fix # eslint --fix
pnpm format # prettier --write
pnpm build # nest build -> dist/
Integration tests need a Docker-compatible socket. On Podman (default on Arch + many Linux setups), enable the user socket once and the suite auto-detects it:
systemctl --user enable --now podman.socket
To override, set DOCKER_HOST before running, e.g.
DOCKER_HOST=unix:///run/user/$UID/podman/podman.sock pnpm test:int.
Architecture
One NestJS module per domain (shoot/, frame/, ...), each with a
controller, a service and its Drizzle schema in src/db/<domain>-schema.ts.
Services join other domains' tables directly where a query needs them, always
scoped to the caller. See CLAUDE.md for the conventions guide.
Deployment
compose.yml runs the production stack: rollnote-db (Postgres 18) and
rollnote-api (built from the Dockerfile, Node 24 alpine, runs as node
on port 3000, joined to the external websites network for the reverse
proxy). Both containers are hardened (no-new-privileges, read-only root
filesystem with tmpfs for the paths they write, capabilities dropped; the
database keeps only what its entrypoint needs to drop to the postgres
user) and have CPU, memory and pid limits plus log rotation.
docker compose up -d --build # reads .env for every ${VAR} in compose.yml
CI (.forgejo/workflows/test.yml, triggered by the run-ci PR label) runs
lint, typecheck, nest build, the openapi.json drift check, the
changelog/front-version check, a gitleaks secret scan, fallow, unit and
integration tests, and a build of the Docker image.