Skip to content

Akhand0ps/splitwise

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

22 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

SPL β€” Splitwise Backend

A Splitwise-inspired expense splitting backend built with Node.js + Express + Prisma for the core API, and Django for admin panel and analytics. Both services share the same PostgreSQL (Neon) database.


Architecture

React (frontend β€” coming soon)
         |
         ↓
Node.js (Express)  ←——————→  PostgreSQL (Neon)
Core API, Auth,                    ↑
Business Logic,                    |
Expense Splitting           (same DB, read-only)
                                   |
Django (Admin + Analytics) β†β€”β€”β€”β€”β€”β€”β€”β”˜
Admin Panel, Reports,
Analytics API

Why two services?

  • Node.js handles high-performance transactional API work
  • Django was chosen for its superior admin interface and Python's data ecosystem for analytics β€” both pointing to the same PostgreSQL instance

Tech Stack

Layer Technology
Core API Node.js, Express, TypeScript
ORM (Node) Prisma
Admin + Analytics Django, Django REST Framework
Database PostgreSQL (Neon)
Auth JWT (jsonwebtoken + bcrypt)
Deployment Render (both services)

Project Structure

/
β”œβ”€β”€ node-api/                  # Node.js service
β”‚   β”œβ”€β”€ prisma/
β”‚   β”‚   └── schema.prisma
β”‚   β”œβ”€β”€ src/
β”‚   β”‚   β”œβ”€β”€ controller/
β”‚   β”‚   β”‚   β”œβ”€β”€ auth.controller.ts
β”‚   β”‚   β”‚   β”œβ”€β”€ group.controller.ts
β”‚   β”‚   β”‚   β”œβ”€β”€ expense.controller.ts
β”‚   β”‚   β”‚   β”œβ”€β”€ settlement.controller.ts
β”‚   β”‚   β”‚   └── balance.controller.ts
β”‚   β”‚   β”œβ”€β”€ routes/
β”‚   β”‚   β”‚   β”œβ”€β”€ auth.routes.ts
β”‚   β”‚   β”‚   β”œβ”€β”€ group.routes.ts
β”‚   β”‚   β”‚   β”œβ”€β”€ expense.routes.ts
β”‚   β”‚   β”‚   β”œβ”€β”€ settlement.routes.ts
β”‚   β”‚   β”‚   └── balance.routes.ts
β”‚   β”‚   β”œβ”€β”€ middlewares/
β”‚   β”‚   β”‚   └── auth.middleware.ts
β”‚   β”‚   β”œβ”€β”€ utils/
β”‚   β”‚   β”‚   └── balance.ts
β”‚   β”‚   β”œβ”€β”€ lib/
β”‚   β”‚   β”‚   └── prisma.ts
β”‚   β”‚   β”œβ”€β”€ app.ts
β”‚   β”‚   └── index.ts
β”‚   β”œβ”€β”€ .env
β”‚   └── package.json
β”‚
└── django-admin/              # Django service
    β”œβ”€β”€ core/
    β”‚   β”œβ”€β”€ settings.py
    β”‚   └── urls.py
    β”œβ”€β”€ analytics/
    β”‚   β”œβ”€β”€ models.py
    β”‚   β”œβ”€β”€ views.py
    β”‚   β”œβ”€β”€ urls.py
    β”‚   └── admin.py
    β”œβ”€β”€ .env
    └── manage.py

Local Setup

Prerequisites

  • Node.js 18+
  • Python 3.10+
  • A Neon DB account (or any PostgreSQL instance)

Node.js API

cd node-api
npm install

Create .env:

DATABASE_URL=postgresql://user:password@ep-xxx.neon.tech/dbname?sslmode=require
JWT_SECRET=your_super_secret_key
PORT=3001

Run migrations and start:

npx prisma migrate dev --name init
npm run dev

API runs at http://localhost:3001


Django Admin

cd django-admin
python -m venv venv
source venv/bin/activate        # Windows: venv\Scripts\activate
pip install django djangorestframework psycopg2-binary python-dotenv dj-database-url

Create .env:

DJANGO_SECRET_KEY=your-django-secret-key
DEBUG=True
DATABASE_URL=postgresql://user:password@ep-xxx.neon.tech/dbname?sslmode=require

Run and create admin user:

python manage.py migrate
python manage.py createsuperuser
python manage.py runserver 8001

Admin panel at http://localhost:8001/admin


API Reference

Auth

Method Endpoint Auth Description
POST /api/v1/auth/register No Register new user
POST /api/v1/auth/login No Login, returns JWT
GET /api/v1/auth/me Yes Get current user

Register

POST /api/v1/auth/register
{
  "name": "Rahul",
  "email": "rahul@example.com",
  "password": "secret123",
  "phone": "9999999999"
}

Login

POST /api/v1/auth/login
{
  "email": "rahul@example.com",
  "password": "secret123"
}

Returns: { user, token }

All protected routes require Authorization: Bearer <token> header


Groups

All group routes require auth.

Method Endpoint Description
POST /api/v1/groups Create a group
GET /api/v1/groups Get my groups
GET /api/v1/groups/:groupId Get group details
POST /api/v1/groups/:groupId/members Add member by email (admin only)
DELETE /api/v1/groups/:groupId/members/:userId Remove member (admin only)
DELETE /api/v1/groups/:groupId/leave Leave a group

Create Group

POST /api/v1/groups
{
  "name": "Goa Trip",
  "description": "Trip expenses"
}

Add Member

POST /api/v1/groups/1/members
{
  "email": "priya@example.com"
}

Expenses

All expense routes require auth.

Note: getGroupExpenses, getExpenseById, and deleteExpense are currently registered as POST in the router (see expense.routes.ts). The intended methods are shown below β€” fix the route definitions if needed.

Method Endpoint Description
POST /api/v1/expenses Add an expense
POST /api/v1/expenses/group/:groupId Get group expenses (registered as POST β€” should be GET)
POST /api/v1/expenses/:expenseId Get single expense (registered as POST β€” should be GET)
POST /api/v1/expenses/:expenseID Delete expense (registered as POST β€” should be DELETE; note param typo expenseID)

Add Expense β€” Equal Split

POST /api/v1/expenses
{
  "description": "Hotel",
  "amount": 900,
  "groupId": 1,
  "splitType": "EQUAL"
}

Add Expense β€” Exact Split

POST /api/v1/expenses
{
  "description": "Dinner",
  "amount": 500,
  "groupId": 1,
  "splitType": "EXACT",
  "customSplits": [
    { "userId": 1, "value": 300 },
    { "userId": 2, "value": 200 }
  ]
}

Add Expense β€” Percentage Split

POST /api/v1/expenses
{
  "description": "Cab",
  "amount": 500,
  "groupId": 1,
  "splitType": "PERCENTAGE",
  "customSplits": [
    { "userId": 1, "value": 60 },
    { "userId": 2, "value": 40 }
  ]
}

Settlements

All settlement routes require auth.

Method Endpoint Description
POST /api/v1/settlements Record a payment
GET /api/v1/settlements/group/:groupId Get group settlements
PATCH /api/v1/settlements/:settlementId/complete Receiver confirms payment

Create Settlement

POST /api/v1/settlements
{
  "toUserId": 1,
  "groupId": 1,
  "amount": 300,
  "note": "Paid via UPI"
}

Flow: Payer creates settlement (PENDING) β†’ Receiver confirms (COMPLETED) β†’ Balance updates automatically


Balances

All balance routes require auth.

Method Endpoint Description
GET /api/v1/balances/group/:groupId Group balances + suggested transactions
GET /api/v1/balances/me My net balance across all groups

Group Balance Response

{
  "balances": [
    { "user": { "id": 1, "name": "Rahul" }, "amount": 600, "status": "owed" },
    { "user": { "id": 2, "name": "Priya" }, "amount": -300, "status": "owes" }
  ],
  "transactions": [
    { "from": { "name": "Priya" }, "to": { "name": "Rahul" }, "amount": 300 }
  ]
}

My Balances Response

{
  "overall": -500,
  "groups": [
    { "group": { "id": 1, "name": "Goa Trip" }, "balance": -300, "status": "owes" },
    { "group": { "id": 2, "name": "Flat" }, "balance": -200, "status": "owes" }
  ]
}

Analytics (Django)

Method Endpoint Description
GET /api/analytics/summary/ Platform overview
GET /api/analytics/top-spenders/ Top paying users
GET /api/analytics/group-activity/ Most active groups
GET /api/analytics/unsettled-debts/ All pending settlements

Optional query params: ?limit=10


Deployment on Render

Node.js Service

  1. Push code to GitHub
  2. Go to render.com β†’ New β†’ Web Service
  3. Connect your repo, select the node-api folder
  4. Set:
    • Build Command: npm install && npx prisma generate && npm run build
    • Start Command: node dist/index.js
  5. Add environment variables:
    DATABASE_URL=...
    JWT_SECRET=...
    PORT=3001
    

Django Service

  1. Go to Render β†’ New β†’ Web Service
  2. Connect your repo, select the django-admin folder
  3. Set:
    • Build Command: pip install -r requirements.txt
    • Start Command: gunicorn core.wsgi:application
  4. Add environment variables:
    DATABASE_URL=...
    DJANGO_SECRET_KEY=...
    DEBUG=False
    
  5. Install gunicorn:
    pip install gunicorn
    pip freeze > requirements.txt

After deploy, create superuser via Render shell:

python manage.py createsuperuser

Database Schema

User ──────────────────────────────────────────┐
 β”‚                                             β”‚
 β”œβ”€β”€β”€ GroupMember ───── Group                  β”‚
 β”‚                        β”‚                    β”‚
 β”‚                        β”œβ”€β”€β”€ Expense ─────────
 β”‚                        β”‚       β”‚            β”‚
 β”‚                        β”‚       └─── ExpenseSplit
 β”‚                        β”‚
 β”‚                        └─── Settlement
 β”‚                                 β”‚
 β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Key Design Decisions

Balances are never stored β€” always calculated live from expenses and settlements. This keeps data consistent and avoids sync issues.

Django uses managed = False β€” Django reads the same Postgres tables Node.js created. It never runs migrations on those tables, just reads them for admin and analytics.

Settlement flow is two-step β€” payer records it as PENDING, receiver confirms as COMPLETED. This prevents fake settlements.

Split types β€” EQUAL auto-divides among all group members. EXACT and PERCENTAGE require explicit per-user values that must sum to total/100%.

About

Backend API for a Splitwise-like expense sharing app built with Node.js, Express 5, TypeScript, Prisma & PostgreSQL

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors