An internal wiki for TigerRacing, a ~40-person student Formula SAE team that replaces about a quarter of itself every year.
Fourteen years of documentation — onboarding material, maintenance procedures, hard-won details about specific cars — currently sits across three disconnected file stores with no naming convention and no way to judge whether the author knew what they were talking about. WikiDocs replaces that pile with persistent, editable article pages, so the same unit of knowledge stays at the same address across generations.
What makes it worth building rather than buying is the permission model: content carries an ordered sensitivity level, and each member holds a separate role at each level. That lets the team restrict genuinely sensitive material while leaving the bulk of its knowledge open — and lets a non-lead member with deep expertise hold editing rights without being promoted.
Everything TigerRacing knows, in one place.
Planning complete. Scaffold in place — frontend, backend and database run end to end; no product features yet.
Delivery is split into two products. v1 is a single-user wiki — no accounts, no logins, no permissions — which removes a hard dependency on an authentication service that does not exist yet. v2 is the permissioned product described above.
| Release | What it is |
|---|---|
| v1 | Single-user wiki: articles, rich-text editing, links, media, tags, revision history, rollback, search, tag browsing. Runs locally. |
| v2 | The permissioned product, plus the auth service it depends on and the AWS deployment. |
| v2.5 | Reader typeface selection. |
| v3 | Legacy importer, mobile, editor dashboard, semantic search. |
Nothing is cancelled — everything deferred is deferred, not dropped.
| Frontend | React 19 · Vite 8 · TipTap — a static SPA, no server rendering |
| Backend | NestJS 11.2 · Node 24 · Drizzle ORM 0.45 — a layered modular monolith |
| Data | PostgreSQL 17 · Postgres full-text search · S3 for media (private) |
| Infra | AWS Lambda + API Gateway · S3 + CloudFront · CDK · GitHub Actions |
Three version pins are deliberate and should not be "upgraded": NestJS 11, not 12 (12 was alpha when this was planned and carries a CommonJS→ESM migration; it has since gone stable, so moving is an open decision — make it before real code piles up). Drizzle 0.45, not 1.0 (release candidate). PostgreSQL 17, not 18 (18 is unavailable on burstable RDS classes — which is also why UUIDs are generated application-side).
Development runs entirely local, with PostgreSQL in Docker pinned to the same major version as RDS. There is no shared dev database and no preview environment pointing at production.
Prerequisites: Node 24 (nvm use reads .nvmrc; newer works locally, but 24 is what production runs) and Docker. New to the local database? backend/database/README.md walks through it step by step.
npm install # one install for every workspace
cp backend/.env.example backend/.env # local-only values, matching docker-compose
npm run db:up # PostgreSQL 17 in Docker; waits until healthy
npm run db:migrate # applies committed migrations (none yet)Then, in two terminals:
npm run dev:backend # API on http://localhost:3000/v1
npm run dev:frontend # app on http://localhost:5173Open http://localhost:5173 — it should say Database: reachable. That one line proves the whole path: frontend → backend → database.
| Command | Does |
|---|---|
npm test |
Backend e2e tests. Needs the database up |
npm run build |
Builds both apps |
npm run db:stop |
Stops the database; your data survives in a Docker volume |
npm run db:generate |
Generates a migration from backend/database/schema.ts |
npm 11 may warn that a few packages' install scripts weren't run (esbuild, @parcel/watcher, unrs-resolver). Everything above works without them.
frontend/ Vite + React SPA. No data access — everything goes through the API
backend/ NestJS API
src/modules/ one folder per owned entity
src/common/filters/ the ONLY place error responses are shaped
database/ everything database: Docker setup, local data, schema,
migrations, the connection pool and Drizzle client
packages/contracts/ types shared by frontend and backend
tools/dev-auth/ v2 placeholder
docs/ sprint tickets, story specs, mockups
In development, Vite proxies /v1 to the backend, so the frontend and API are same-origin — as they will be in production — and there is no CORS to configure.
Read DESIGN.md. It is the complete standalone record — product, permission model, release plan, architecture, design system, non-goals, accepted risks, and open questions.
Two sections in particular are worth reading before the first commit:
- The five seams — five structural decisions v1 must build even though v1 does not need them. They are cheap now and expensive-to-impossible later. A v1 story that drops one of these is a defect, not a simplification. One of them is not reversible.
- The decision that reshaped the design — why a section is a database row rather than part of a text blob, and the two rounds of data-loss bug that produced the current write model. It explains why several rules look unusual.
Target is under ~$40/month at 40 members, verified at roughly $17–19 with headroom. Cost is the first build-vs-buy justification, so a change that materially raises it reopens the decision to build. A NAT Gateway alone (+$32.85/month) would break the envelope.