Backend for Recta — personal finance app with household collaboration. Open source & self-hostable.
About this project
Recta is an open source personal finance manager. This repository is the public backend: anyone can self-host it, fork it, submit pull requests, or build on top of it. Submitting PRs or contributing does not guarantee that any feature or change will be incorporated into the hosted product at recta.app. The maintainers decide what is merged and what is shipped on recta.app.
This project is maintained by PrimoDev.
Other part of the project: recta-selfhosted-frontend — web app (React/Vite).
Tech stack
- Node.js 20+
- Fastify – REST API
- PostgreSQL – database
- Prisma – ORM
- Firebase Admin – auth (Google, Apple, email)
- Zod – validation
Hosting (self-hosted)
You can run this backend on any Node.js host. Some options:
- Railway – simple deploy, PostgreSQL add-on, cron support
- Render – free tier, PostgreSQL, background workers
- Fly.io – global regions, PostgreSQL via Supabase or external
- DigitalOcean App Platform – managed app + DB
- VPS (Hetzner, Linode, etc.) – run
npm run startbehind Nginx and use a managed PostgreSQL (e.g. Supabase, Neon, or self-hosted)
Set DATABASE_URL, pick an auth mode and, in production, ALLOWED_ORIGINS and optionally SWAGGER_USERNAME/SWAGGER_PASSWORD. For recurring transactions, schedule npm run cron:process-recurrences once per day (cron job or Railway cron).
You don't need Firebase to run a working instance — local auth mode plus PostgreSQL is enough.
How to run
Prerequisites
- Node.js 20+
- PostgreSQL 16+
- Firebase project with Authentication enabled
1. Install dependencies
npm install2. Environment variables
cp env.example .envEdit .env:
| Variable | Required | Description |
|---|---|---|
DATABASE_URL |
Yes | PostgreSQL URL, e.g. postgresql://user:password@localhost:5432/recta |
AUTH_MODE |
Yes | local (no Firebase) or firebase (default). See below. |
Auth mode
Choose how users sign in via AUTH_MODE:
local(recommended for self-host) — no third-party auth. The backend issues and verifies its own JWTs for email/password login. RequiresAUTH_JWT_SECRET(min 32 chars; generate withopenssl rand -base64 48). Optional:AUTH_TOKEN_TTL_HOURS(default 720 = 30 days),AUTH_REQUIRE_EMAIL_VERIFICATION(defaultfalse). No Firebase setup needed.firebase(default) — Google/Apple/Email sign-in via Firebase. Provide credentials with one of the two options below. Optionally set the publicAUTH_FIREBASE_WEB_API_KEYsoGET /auth/confighands the client your web config.
Clients call GET /auth/config (public) to discover the mode before showing a login screen.
Firebase – option A (file):
- Create a service account and download the JSON.
- Save it in the project (e.g.
firebase-service-account.json) and do not commit it. - In
.env:GOOGLE_APPLICATION_CREDENTIALS=./firebase-service-account.json
Firebase – option B (env vars):
- In
.envset:FIREBASE_PROJECT_ID,FIREBASE_CLIENT_EMAIL,FIREBASE_PRIVATE_KEY(private key with\nfor newlines).
Other optional vars: PORT (default 3000), REDIS_URL, SWAGGER_USERNAME/SWAGGER_PASSWORD, ALLOWED_ORIGINS, FIRST_RUN. See env.example.
In production use
npm run db:deploy(notdb:migrate) to apply migrations, or setFIRST_RUN=trueon the first boot to run them automatically, then remove it.
3. Run migrations
npm run db:migrate4. Start the server
Development:
npm run devProduction:
npm run build
npm run startAPI at http://localhost:3000. Swagger docs (when enabled): http://localhost:3000/docs.
5. Verify
curl http://localhost:3000/healthReturns { status, timestamp, uptime, apiVersion, minClientVersion }. apiVersion is the self-host
contract: clients compare it against the official contract to detect a backend that has fallen behind.
If your instance reports an older apiVersion than the app expects, pull the latest code and redeploy
so the schema and endpoints stay in sync.
Scripts
| Command | Description |
|---|---|
npm run dev |
Dev server with hot reload |
npm run build |
Production build |
npm run start |
Run production build |
npm run db:migrate |
Run migrations (dev) |
npm run db:deploy |
Run migrations (production) |
npm run db:studio |
Open Prisma Studio |
npm run db:generate |
Generate Prisma Client |
npm run lint |
Lint |
npm run typecheck |
Type check |
Recurring transactions (cron)
To process recurring transactions daily, run as a cron job:
npm run build
npm run cron:process-recurrencesSchedule this once per day (cron, systemd, or your host’s scheduler).
Project structure
src/
├── app.ts # Fastify app
├── index.ts # Entry point
├── modules/ # API modules (auth, users, households, accounts, transactions, etc.)
├── shared/
│ ├── config/ # env, Firebase
│ ├── db/ # Prisma & migrations
│ ├── middleware/ # Auth & authorization
│ └── utils/
└── jobs/ # Cron (e.g. processRecurrences)
License
MIT