Backend for the self-hosted personal finance tracker. Node 20 + Express + Prisma + PostgreSQL.
cp .env.example .env
# Edit DATABASE_URL and SESSION_SECRET
docker run -d --name ft-pg -e POSTGRES_PASSWORD=postgres -e POSTGRES_DB=finance -p 5432:5432 postgres:16-alpine
pnpm install
pnpm exec prisma migrate deploy
pnpm devAPI listens on http://localhost:4000.
pnpm test
pnpm typecheck
pnpm lint
pnpm build- Add the PostgreSQL plugin to your Railway project.
- Create a service from this repo. Railway auto-detects
Dockerfile(configured viarailway.json). - Set environment variables:
DATABASE_URL→ reference the Postgres plugin's variableSESSION_SECRET→ 32+ random bytes (openssl rand -hex 32)WEB_ORIGIN=https://<your-web-domain>(must match the web frontend)DEFAULT_CURRENCY=USDSIGNUP_ENABLED=true(set tofalseafter first user registers)LOG_LEVEL=info
- Healthcheck path:
/api/ready(already inrailway.json). - Set up a public domain.
POST /api/auth/token/new— create new anonymous user, returns token oncePOST /api/auth/token/login— login with tokenPOST /api/auth/token/rotate— rotate tokenGET /api/auth/me,PATCH /api/auth/me,POST /api/auth/logoutGET/POST/PATCH/DELETE /api/accountsGET/POST/PATCH/DELETE /api/categoriesGET/POST/PATCH/DELETE /api/tagsGET/POST/PATCH/DELETE /api/transactionsGET/POST/PATCH/DELETE /api/budgets,GET /api/budgets/progressGET/POST/PATCH/DELETE /api/recurring,POST /api/recurring/:id/runGET/POST/PATCH/DELETE /api/goals,POST /api/goals/:id/contributeGET /api/reports/summary|cashflow|by-category,GET /api/reports/export.csvPOST /api/import/csvGET /api/health(liveness),GET /api/ready(DB readiness)
- Auth: token-only (web3-like). User has a
tokenHash(sha256 of raw token). The raw token is shown once at creation. - CSRF: double-submit cookie. Mutating requests need
X-XSRF-TOKENheader that matchesXSRF-TOKENcookie. - Session: iron-session, httpOnly cookie.
- Money:
Decimal(14,2)end-to-end.decimal.jsfor math. - Recurring:
node-cronhourly, idempotent via(recurringRuleId, scheduledFor)unique constraint.