Bahhi Khata is a backend-first expense tracker built as a proof-of-work engineering project. The focus is on correct backend design, schema ownership, authentication, and real-world evolution, with the frontend intentionally kept simple until the data layer is solid.
This project is meant to resemble a digital khata (ledger) — reliable, structured, and honest — rather than a flashy dashboard.
- Backend correctness first — APIs, auth, and schema before UI polish
- Schema evolves with features — migrations over guesswork
- No premature abstraction — clarity > cleverness
- Real-world constraints — production DB, cloud hosting, auth flows
- Backend API: https://bahhi-khata-backend.onrender.com (Render free tier — may cold start)
- Frontend: Local / in progress
- Node.js 18+
- PostgreSQL 16+ (local or Neon)
- Git
git clone https://github.com/BahhiKhata/BahhiKhata.git
cd BahhiKhata/backend
npm install
cp .env.example .envInitialize database:
psql <DATABASE_URL> -f database/schema.sqlStart server:
npm run dev
# or
npm startcd frontend
npm install
npm run devFrontend (Next.js)
│
▼
Backend API (Express + JWT)
│
▼
PostgreSQL (Neon)
- Stateless JWT authentication
- Strict user-level data ownership
- Hosted on Render + Neon
backend/
├── server.js
├── config/db.js
├── routes/
│ ├── auth.js
│ ├── expenses.js
│ └── expenseTypes.js
├── middleware/auth.js
├── database/
│ ├── schema.sql
│ └── migrations/
└── utils/
frontend/
├── pages/
│ ├── login.jsx
│ ├── register.jsx
│ ├── expenses.jsx
│ ├── add-expense.jsx
│ └── edit-expense/[id].jsx
├── components/
│ └── Layout.jsx
├── utils/api.js
CREATE EXTENSION IF NOT EXISTS "pgcrypto";
CREATE TABLE users (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name VARCHAR(100) NOT NULL,
email VARCHAR(100) UNIQUE NOT NULL,
password_hash TEXT NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE expense_types (
id SERIAL PRIMARY KEY,
name VARCHAR(50) UNIQUE NOT NULL,
description TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE expenses (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
expense_type_id INTEGER NOT NULL REFERENCES expense_types(id),
amount NUMERIC(10,2) NOT NULL CHECK (amount > 0),
description TEXT,
expense_date DATE NOT NULL DEFAULT CURRENT_DATE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX idx_expenses_user_id ON expenses(user_id);
CREATE INDEX idx_expenses_date ON expenses(expense_date);
CREATE INDEX idx_expenses_type ON expenses(expense_type_id);POST /api/auth/registerPOST /api/auth/login
GET /api/expenses(supports filters)POST /api/expensesPUT /api/expenses/:idDELETE /api/expenses/:id
Filtering supported via query params:
/api/expenses?from=YYYY-MM-DD&to=YYYY-MM-DD&min=100&max=500
- JWT-based authentication
- Expense CRUD (add / edit / delete)
- User-scoped data access
- Date & amount filtering
- Production DB on Neon
- Schema migration experience
- Minimal but functional frontend
Phase 3 intentionally focuses on correctness over polish.
- Monthly spending summary
- Category-wise totals
- Daily averages
- Dedicated
/statspages
- Better edit flows (inline / modal)
- Debounced filters
- Pagination
- Performance improvements
- Ledger-style layout
- Date-grouped expenses
- Calm, paper-like design language
MIT License
⭐ *This repository represents real backend learning: schema drift, migrations, auth, and production debugging — not just UI