You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
REST API and WebSocket server for SplitEase β a full-stack expense-splitting application that lets friend groups track shared costs, split bills across multiple currencies, and settle debts with optimized payment flows.
The codebase follows a strict Route β Controller β Service β Model layered pattern. Each layer has a single responsibility; no layer skips another.
HTTP Request
βββΊ Route (express Router)
βββΊ Middleware (auth, rate-limit, validation, cache)
βββΊ Controller (parse req, call service)
βββΊ Service (business logic, DB queries)
βββΊ Model (Mongoose)
βββ throws AppError on failure
βββ formats and sends res.json()
βββ global error handler catches AppError β standard { message } shape
Database Schema
User
Field
Type
Notes
fullName
String
required
email
String
unique, indexed
gender
String
Male / Female / Other
password
String
bcrypt, 12 rounds
profilePic
String
Cloudinary URL
friends
[ObjectId]
ref: User, bi-directional
paymentMethods
[Object]
{ type, identifier }
resetPasswordToken
String
SHA-256 hash
resetPasswordExpires
Date
1-hour TTL
Group
Field
Type
Notes
name
String
max 30 chars
description
String
max 100 chars
type
String
Travel / Household / Event / Work / Friends
members
[ObjectId]
ref: User, includes creator
createdBy
ObjectId
ref: User
completed
Boolean
true when all debts settled
Expense
Field
Type
Notes
payer
ObjectId
ref: User (who paid)
participants
[ObjectId]
ref: User (excludes payer)
totalAmount
Number
currency
String
INR / USD / EUR / GBP / JPY
description
String
max 30 chars
splitMethod
String
Equal / Percentage / Custom
splitDetails
[Object]
{ userId, amountOwed, transactionId }
groupId
ObjectId
ref: Group, optional
type
String
expense category
expenseStatus
Boolean
true when all splits settled
Transaction
Field
Type
Notes
transactionId
String
URL-safe UUID (replaces bcrypt hash)
expenseId
ObjectId
ref: Expense
sender
ObjectId
ref: User (owes money)
receiver
ObjectId
ref: User (is owed)
amount
Number
currency
String
mode
String
UPI / PayPal / Stripe
status
String
Pending / Success / Failed
ExchangeRate
Field
Type
Notes
base
String
INR
rates
Map
currency β rate
timestamp
Date
updated daily at midnight
Security
Measure
Implementation
Password hashing
bcryptjs, 12 salt rounds everywhere
Token auth
JWT (7-day expiry) + Redis session validation on every request
Security headers
helmet() + custom Cross-Origin-* headers, applied before all routes
CORS
Allowlist: localhost:3000 + FRONTEND_URL env var
Rate limiting
express-rate-limit on auth, profile, group, and expense routes
Input validation
express-validator at route layer + service-layer ObjectId guards
Password reset
SHA-256 hashed token stored in DB; raw token only in email link
Anti-enumeration
forgotPassword returns 200 for unknown emails (no account disclosure)
File uploads
Multer type/size validation before Cloudinary upload
Graceful shutdown
server.close() β Redis shutdown β process.exit(0) on SIGINT
API Reference
Interactive Swagger docs are served at /api/docs when the server is running.
Authentication β /api/auth
Method
Path
Auth
Description
POST
/signup
β
Register new user
POST
/login
β
Login, returns JWT
GET
/google/login
β
Redirect to Google OAuth
GET
/google/callback
β
Google OAuth callback
POST
/logout
JWT
Invalidate session
POST
/forgot-password
β
Send password reset email
POST
/reset-password
β
Reset password with token
Profile β /api/profile
Method
Path
Auth
Description
GET
/me
JWT
Get own profile
PUT
/update
JWT
Update name / gender
POST
/upload
JWT
Upload profile picture
PUT
/change-password
JWT
Change password
POST
/add-friend
JWT
Add friend by ID (bi-directional)
POST
/search-friends
JWT
Search users by name
DELETE
/delete-friend/:friendId
JWT
Remove friend
POST
/add-payment
JWT
Add payment method
DELETE
/delete-payment/:paymentId
JWT
Remove payment method
Groups β /api/groups
Method
Path
Auth
Description
POST
/create
JWT
Create group
GET
/mygroups
JWT
List user's groups with stats
GET
/:groupId
JWT
Group detail, expenses, transactions
PUT
/edit/:groupId
JWT
Edit description / members / status
DELETE
/delete/:groupId
JWT
Delete group and cascade
GET
/friends
JWT
List user's friends
GET
/:groupId/debt-summary
JWT
Optimised settlement plan
Expenses β /api/expenses
Method
Path
Auth
Description
POST
/create
JWT
Create expense + transactions
GET
/group/:groupId
JWT
List expenses for a group
GET
/my-expenses
JWT
List user's expenses (payer or participant)
GET
/expense/:expenseId
JWT
Single expense detail
DELETE
/delete/:expenseId
JWT
Delete expense + cascade transactions
GET
/summary
JWT
Expense summary analytics
GET
/recent
JWT
Recent expenses
GET
/breakdown/:currency
JWT
Category breakdown in given currency
Transactions β /api/transactions
Method
Path
Auth
Description
GET
/pending
JWT
Pending transactions (user as sender)
GET
/history
JWT
Last 10 settled/failed transactions
PUT
/:transactionId/settle
JWT
Settle or fail a transaction
GET
/recent
JWT
Recent transactions for dashboard
Dashboard β /api
Method
Path
Auth
Description
GET
/stats
JWT
Aggregated dashboard statistics
Health β /api/health
Method
Path
Auth
Description
GET
/
β
Liveness probe (returns { status: "ok" })
Real-time Events
Socket.IO events are published via Redis Pub/Sub and forwarded to connected clients. Clients must authenticate their socket connection with a valid JWT.
SMTP credentials β Gmail App Password or any SMTP provider
Installation
# Clone the repository
git clone https://github.com/CodeTirtho97/SplitEase_backend.git
cd SplitEase_backend
# Install dependencies
npm install
# Copy the example env file and fill in your values
cp .env.example .env
Running the server
# Development (hot-reload via nodemon)
npm run dev
# Production
npm start
The server starts on the port defined in PORT (default 5000).
Swagger docs are available at http://localhost:5000/api/docs.
Environment Variables
Copy .env.example to .env and set all values before starting the server.
Variable
Required
Description
PORT
No
HTTP port (default: 5000)
NODE_ENV
No
development / production / test
MONGO_URI
Yes
MongoDB connection string
JWT_SECRET
Yes
Min 32-char random string for JWT signing
REDIS_URL
Yes
Redis connection URL (rediss://β¦)
FRONTEND_URL
Yes
Allowed CORS origin for the frontend
BACKEND_GOOGLE_CALLBACK_URL
Yes
Full URL for OAuth callback (/api/auth/google/callback)
GOOGLE_CLIENT_ID
Yes
Google OAuth client ID
GOOGLE_CLIENT_SECRET
Yes
Google OAuth client secret
CLOUDINARY_CLOUD_NAME
Yes
Cloudinary cloud name
CLOUDINARY_API_KEY
Yes
Cloudinary API key
CLOUDINARY_API_SECRET
Yes
Cloudinary API secret
EMAIL_USER
Yes
SMTP sender email address
EMAIL_PASS
Yes
SMTP app password
EXCHANGE_RATE_URL
No
ExchangeRates API base URL
EXCHANGERATES_API_KEY
No
ExchangeRates API key
BASE_CURRENCY
No
Base currency for rate conversion (default: INR)
Testing
Tests use Jest with mongodb-memory-server β no external MongoDB or Redis connection is required. Redis is fully mocked.
Running tests
# Run full test suite (lint + all tests β recommended before every commit)
npm run validate
# Run all tests only
npm test# Unit tests (pure functions β splitCalculator, debtSimplifier)
npm run test:unit
# Integration tests (service + HTTP route tests against in-memory MongoDB)
npm run test:integration
# Full suite with coverage report
npm run test:coverage
Test structure
tests/
βββ setup/
β βββ globalSetup.js # Spins up MongoMemoryServer once for all suites
β βββ globalTeardown.js # Tears down MongoMemoryServer after all suites
β βββ jestSetup.js # Connects Mongoose; wipes collections between files
β βββ setEnvVars.js # Injects all env vars before any module loads
βββ __mocks__/
β βββ redisMock.js # Replaces config/redis β storeSession, validateSession, etc.
βββ utils/
β βββ splitCalculator.test.js # 13 tests β Equal, Percentage, Custom splits + float tolerance
β βββ debtSimplifier.test.js # 13 tests β empty input, circular debts, net balance, reduction %
βββ services/
β βββ group.test.js # 12 tests β CRUD, debt summary, authorisation
β βββ expense.test.js # 10 tests β creation, splits, cascading delete, validation
β βββ transaction.test.js # 8 tests β pending list, settle flow, history
βββ auth.test.js # 8 tests β signup, login, logout, password reset
βββ profile.test.js # 6 tests β profile fetch, update, add friend, friend errors
Total: 70 tests across 7 suites β all passing.
Test isolation guarantees
Each test file gets a clean database state (collections wiped in afterAll)
Redis is mocked at the module level via moduleNameMapper in jest.config.js
Cron jobs are disabled when NODE_ENV=test
The HTTP server only starts when server.js is the entry point (not when imported by tests)
Tests run in a deterministic order via jestTestSequencer.js (utils β auth β services)
CI/CD Pipeline
GitHub Actions runs on every push and pull request to master / main.
Push / PR to master
βββΊ Install dependencies (npm ci, cached)
βββΊ Lint (ESLint β zero warnings policy)
βββΊ Unit tests (splitCalculator, debtSimplifier)
βββΊ Integration tests (services + API routes)
βββΊ Coverage report (non-blocking, uploaded as artifact)
The pipeline sets only the env vars required for tests β no real MongoDB, Redis, or external API credentials are needed in CI.