Monorepo with two independent Node.js packages:
| Directory | Role | Tech | Dev port |
|---|---|---|---|
root ./ |
Frontend SPA | React 19 + Vite 8 + Tailwind 4 + React Query | :5173 |
backend/ |
REST API | Express 5 + Prisma + SQLite | :3001 |
| Docker Compose | Full stack | nginx → backend | :3000 → :3001 |
npm run dev— Vite dev server (:5173)npm run build—vite build→dist/npm run lint— ESLint on.ts,.tsx, max-warnings 150
npm run dev—tsx watch src/index.tsnpm run build—tsc→dist/npm run start—node dist/index.jsnpx prisma db push— sync schema (used in production Docker entrypoint)npx prisma migrate dev— dev migration workflownpx prisma generate— required before build/start
npm run setup— one-command setup (installs deps, creates DB, generates Prisma)npm start— runs backend + frontend concurrently
bash install.shorcurl -sSL .../install.sh | bash— one-line setupbash uninstall.sh— removes container, volume, and image- Builds and runs single Docker image with SQLite
docker-compose up -d --build— full stack atlocalhost:3000- Copy
.env.example→.env(root) first
- Frontend entry:
src/main.tsx→App.tsx(hash-based routing, no react-router) - Backend entry:
backend/src/index.ts - All API endpoints under
/api/*, token viaAuthorization: Bearer <fokus_token> - JWT stored in
localStoragekeyfokus_token - 401 response auto-clears token and dispatches
auth:logoutevent - i18n: Turkish (
tr) and English (en) via i18next; default reads fromlocalStorage i18nextLng - Background images toggle between
/light.pngand/dark.pngor user-uploaded custom - Spotlight feature (global search, triggered by
/key) - Sidebar modes:
open/hover/closed, persisted inlocalStorage sidebarMode
- TypeScript strict mode,
noUnusedLocals,noUnusedParameters - Frontend uses
"moduleResolution": "bundler"with@/path alias →src/ - Backend uses
"moduleResolution": "NodeNext"with explicit.jsextensions in imports - Prettier: semicolons, single quotes, no trailing commas, 100 printWidth, 2 spaces
- ESLint ignores
backend/from root, must be run inbackend/independently - Tailwind dark mode via
classstrategy (usedark:variants) - Custom breakpoints:
xxs: 350px,xs: 475px - Custom color tokens:
primary-{50..900}(slate-based),gray-900: rgb(32,32,32)
- Frontend: Vitest (
npm testin root) — tests insrc/**/*.test.ts - Backend: Vitest (
npm testinbackend/) — tests inbackend/src/**/*.test.ts - CI runs
npm testfor both frontend and backend
- Single schema:
prisma/schema.prisma— SQLite for all environments - Uses
engineType = "library"withbinaryTargets = ["native"]for reliable cross-platform engine downloads - Production Dockerfile runs
npx prisma db push(not migrate) on container start npx prisma generategenerates the Prisma client
- Release: tag
v*.*.*→ GitHub Actions builds Docker images, pushes toghcr.io, creates release - Frontend served via nginx, SPA fallback (
try_files $uri /index.html) - Dockerfiles are Coolify-compatible
| Key | Purpose |
|---|---|
fokus_token |
Auth JWT |
activeView |
Last active view (hash routing) |
sidebarMode |
"open", "hover", or "closed" |
theme |
"light", "dark", or "system" |
bgImage / isGlobalBg |
Background image settings |
i18nextLng |
Language preference |
fokus_onboarding_pending / fokus_onboarding_done_{uid} |
Onboarding state |
Global: 100 requests per 15 minutes per IP.