A personal finance tracker you control straight from Telegram.
Log expenses and income in seconds, organize them into categories, and get instant multi-currency summaries — all backed by a clean, documented REST API.
- 💬 Telegram-first UX — add expenses and income through a conversational bot powered by grammY. No app to install.
- 🗂️ Categories with emoji — organize spending and earnings into custom, emoji-tagged categories.
- 🌍 Multi-currency — every entry is stored in its original currency and normalized to USD for unified reporting. The default currency is even inferred from the user's timezone.
- 📊 Summaries & stats — instant breakdowns of where your money goes.
- 🔐 Flexible auth — email/password, Google Sign-In, and Telegram login, with JWT access tokens and HTTP-only cookie refresh tokens.
- 🪪 Plans & subscriptions — tiered limits (categories / expenses / incomes) baked into the data model.
- 📚 Self-documenting API — Swagger UI generated automatically in non-production environments.
- 🛡️ Hardened by default — request validation (Zod + class-validator), rate limiting (
@nestjs/throttler), and CORS allow-listing.
| Layer | Technology |
|---|---|
| Runtime | Node.js + TypeScript |
| Framework | NestJS 11 on Fastify |
| Telegram bot | grammY |
| Database | PostgreSQL via Prisma 7 ORM |
| Auth | Passport + JWT, Google & Telegram strategies, bcrypt |
| Validation | Zod (env) + class-validator / class-transformer (DTOs) |
| Tooling | Biome (lint + format), Jest |
| Delivery | Docker, Docker Compose, GitHub Actions → GHCR → VPS |
┌──────────────┐ webhook ┌─────────────────────────┐
Telegram ◄─────►│ Telegram │ ───────────────► │ NestJS + Fastify API │
user │ (grammY bot) │ │ │
└──────────────┘ │ ┌────────────────────┐ │
│ │ Auth (JWT/Google/ │ │
Web / API ◄────────── REST + Swagger ──────────────┤ │ Telegram) │ │
clients │ ├────────────────────┤ │
│ │ Expenses / Incomes │ │
│ │ Categories │ │
│ │ Currency / Summary │ │
│ │ Users / Plans │ │
│ └────────────────────┘ │
└────────────┬─────────────┘
│ Prisma
▼
┌───────────────┐
│ PostgreSQL │
└───────────────┘
The codebase is organized into feature modules under src/modules/ (auth, expenses, incomes, expense-categories, income-categories, currency, transactions, users), with the Telegram integration isolated in src/bot/ and shared utilities in src/common/.
- Docker & Docker Compose
- A Telegram bot token (from
@BotFather) - ngrok — Telegram webhooks require HTTPS, so ngrok tunnels your local server during development
Create a .env file in the project root:
PORT=3000
DATABASE_URL=postgresql://user:password@db:5432/limebalance
BOT_TOKEN=your-telegram-bot-token
WEBHOOK_URL=https://your-ngrok-subdomain.ngrok-free.app
CORS_ORIGIN=http://localhost:5173
GOOGLE_CLIENT_ID=your-google-client-id
JWT_SECRET=a-secret-of-at-least-32-charactersEnvironment variables are validated at startup against
src/config/env.schema.ts— the app refuses to boot with an invalid configuration.
make devThis spins up the database and app via Docker Compose and starts an ngrok tunnel. Then point Telegram at your webhook:
make set-webhookmake migrate # create & apply during development
make migrate-create name=add_x # create a named migration
make migrate-deploy # apply pending migrations (production)
make migrate-status # check migration stateInspect data visually with Prisma Studio:
make db-studio # http://localhost:5555npm install
npm run start:dev # watch mode
npm run start:prod # run compiled build from dist/
npm run lint # Biome checks
npm run lint:fix # Biome auto-fix
npm run format # Biome format
npm run test # unit tests
npm run test:e2e # end-to-end tests
npm run test:cov # coverageWith the app running in a non-production environment, interactive Swagger docs are available at:
http://localhost:3000/api/docs
All routes are served under the /api prefix. A few highlights from the auth module:
| Method | Endpoint | Description |
|---|---|---|
| POST | /api/auth/register |
Register with email & password |
| POST | /api/auth/login |
Log in and receive tokens |
| POST | /api/auth/google |
Sign in with Google |
| POST | /api/auth/telegram |
Sign in with Telegram |
| POST | /api/auth/refresh |
Rotate the access token |
| GET | /api/auth/me |
Get the current user |
| POST | /api/bot/webhook |
Telegram webhook entry point |
Pushing to the repository triggers the GitHub Actions workflow in .github/workflows/:
push ──► GitHub Actions
├── Builder stage → compile TS → dist/
└── Production stage → dist/ + prod node_modules → ~150MB image
│
▼
ghcr.io/<owner>/limebalance:latest
│
▼
VPS: docker pull → docker run
The production image carries only the compiled output and runtime dependencies — no build toolchain — keeping it small and fast to ship.
src/
├── bot/ # Telegram bot: controller, service, state & handlers
│ └── handlers/ # start, expense, income, category, stat
├── common/ # currency/timezone utilities, shared DTOs
├── config/ # Zod-validated environment schema
├── modules/
│ ├── auth/ # JWT, Google & Telegram strategies, guards, decorators
│ ├── expenses/ # & expense-categories
│ ├── incomes/ # & income-categories
│ ├── currency/ # conversion & summary helpers
│ ├── transactions/
│ └── users/
├── prisma/ # Prisma service
└── main.ts # bootstrap, CORS, validation, Swagger
prisma/ # schema & migrations
scripts/ # operational scripts (e.g. db backup)
This project is currently private and unlicensed (UNLICENSED).
Built with NestJS, Prisma & grammY by Elijah Pavlov.