Skip to content

About

A backend ledger system demonstrating double-entry accounting, atomic transactions, idempotency, JWT authentication, and immutable financial records.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

27 Commits

Folders and files

Repository files navigation

Ledger API

A RESTful backend API for managing users, accounts, and financial transactions using Node.js, Express, MongoDB, and Mongoose.

The project uses a double-entry ledger approach, where account balances are calculated from immutable debit and credit ledger entries rather than being stored and directly updated as a balance.

Features

  • User registration and login
  • JWT-based authentication
  • HTTP-only cookie authentication
  • Token blacklist-based logout
  • Password hashing with bcrypt
  • User account creation and retrieval
  • Account balance calculation from ledger entries
  • Credit and debit ledger entries
  • Atomic transactions using MongoDB sessions
  • Transaction idempotency using unique idempotency keys
  • Transaction status tracking
  • System-user authorization for initial funds
  • Immutable ledger entries
  • Automatic expiration of blacklisted JWTs

Tech Stack

  • Node.js
  • Express.js
  • MongoDB
  • Mongoose
  • JWT (JSON Web Tokens)
  • bcryptjs
  • cookie-parser
  • dotenv

Project Structure

ledger-api/
├── server.js
├── package.json
├── .env.example
└── src/
    ├── app.js
    ├── config/
    │   └── db.js
    ├── controllers/
    │   ├── account.controller.js
    │   ├── auth.controller.js
    │   └── transaction.controller.js
    ├── middleware/
    │   └── auth.middleware.js
    ├── models/
    │   ├── account.model.js
    │   ├── blackList.model.js
    │   ├── ledger.model.js
    │   ├── transaction.model.js
    │   └── user.model.js
    └── routes/
        ├── account.route.js
        ├── auth.routes.js
        └── transaction.routes.js

Architecture

The application follows a simple layered architecture:

Client
  │
  ▼
Express Routes
  │
  ▼
Authentication Middleware
  │
  ▼
Controllers
  │
  ▼
Mongoose Models
  │
  ▼
MongoDB

Ledger-based balance

Account balances are not stored as a mutable balance field.

Instead, the balance is calculated from ledger entries:

Balance = Total Credits - Total Debits

For example:

Account A

Credit   +1000
Debit     -250
Credit    +500
----------------
Balance  = 1250

This provides an auditable transaction history and avoids directly mutating an account's balance.

Data Models

User

Stores application users.

Important fields:

  • name
  • email
  • password
  • systemUser

Passwords are hashed with bcryptjs before being stored.

Account

Represents a user's financial account.

Important fields:

  • user
  • status
  • currency

Supported account statuses:

  • ACTIVE
  • FROZEN
  • CLOSED

The default currency is INR.

Transaction

Represents a transfer between two accounts.

Important fields:

  • fromAccount
  • toAccount
  • amount
  • status
  • idempotencyKey

Supported transaction statuses:

  • PENDING
  • COMPLETED
  • FAILED
  • REVERSED

Ledger

Represents an individual financial movement.

Each transaction creates:

  1. A DEBIT ledger entry for the source account
  2. A CREDIT ledger entry for the destination account

Ledger entries are intentionally immutable and cannot be updated or deleted through the model's protected operations.

Token Blacklist

Used to invalidate JWTs after logout.

Blacklisted tokens automatically expire from MongoDB after three days.


Getting Started

Prerequisites

Make sure you have the following installed:

  • Node.js
  • npm
  • MongoDB

A MongoDB Atlas database can also be used.

Installation

Clone the repository:

git clone <repository-url>
cd backend-ledger

Install dependencies:

npm install

Environment Variables

Create a .env file in the project root:

MONGO_URI=mongodb://localhost:27017/ledger-api
JWT_SECRET=your-super-secret-jwt-key
PORT=3000

You can use .env.example as a starting point.

Never commit your .env file or production secrets to version control.

Running the Application

Development

npm run dev

Production

npm start

The server runs on:

http://localhost:3000

API Documentation

The API is organized into three main resources:

/api/auth
/api/accounts
/api/transactions

Authentication

Register

POST /api/auth/register

Request body:

{
  "name": "John Doe",
  "email": "john@example.com",
  "password": "password123"
}

Response:

{
  "user": {
    "_id": "USER_ID",
    "name": "John Doe",
    "email": "john@example.com"
  },
  "token": "JWT_TOKEN"
}

Login

POST /api/auth/login

Request body:

{
  "email": "john@example.com",
  "password": "password123"
}

Logout

POST /api/auth/logout

The current JWT is added to the blacklist and the authentication cookie is cleared.


Accounts

All account endpoints require authentication.

Authentication can be provided through the HTTP-only token cookie or an Authorization header:

Authorization: Bearer <JWT_TOKEN>

Create Account

POST /api/accounts

Creates an account for the authenticated user.

Get User Accounts

GET /api/accounts

Returns all accounts belonging to the authenticated user.

Get Account Balance

GET /api/accounts/balance/:accountId

Returns the current balance calculated from the account's ledger entries.

Example response:

{
  "accountId": "ACCOUNT_ID",
  "message": "Account balance retrieved successfully",
  "balance": 1000
}

Transactions

Create Transaction

POST /api/transactions

Creates a transfer between two accounts belonging to the authenticated user.

Request body:

{
  "fromAccount": "SOURCE_ACCOUNT_ID",
  "toAccount": "DESTINATION_ACCOUNT_ID",
  "amount": 250,
  "idempotencyKey": "unique-request-id-123"
}

Transaction Flow

When a transaction is created:

Validate request
      │
      ▼
Verify source & destination accounts
      │
      ▼
Check idempotency key
      │
      ▼
Verify accounts are ACTIVE
      │
      ▼
Check available balance
      │
      ▼
Start MongoDB transaction
      │
      ├── Create transaction
      │
      ├── Create DEBIT ledger entry
      │
      ├── Create CREDIT ledger entry
      │
      └── Mark transaction COMPLETED
      │
      ▼
Commit MongoDB transaction

If an error occurs during the database transaction, the MongoDB transaction is aborted.

Idempotency

Every transaction requires an idempotencyKey.

The key prevents the same request from being processed multiple times.

For example, if a client sends:

{
  "fromAccount": "ACCOUNT_A",
  "toAccount": "ACCOUNT_B",
  "amount": 500,
  "idempotencyKey": "payment-12345"
}

and retries the exact request using the same key, the backend recognizes the existing transaction instead of creating another one.

This is particularly useful when clients retry requests because of network failures or timeouts.


Initial Funds

The API also provides a system-user-only endpoint for creating initial funds:

POST /api/transactions/system/initial-funds

Request body:

{
  "toAccount": "ACCOUNT_ID",
  "amount": 10000,
  "idempotencyKey": "initial-funds-001"
}

This endpoint requires the authenticated user to have:

systemUser = true

The operation creates the corresponding debit and credit ledger entries inside a MongoDB transaction.


Authentication

JWTs are generated during registration and login.

Tokens:

  • Expire after 3 days
  • Are stored in an HTTP-only cookie
  • Can also be supplied through the Authorization header
  • Are checked against the token blacklist

Example:

Authorization: Bearer <JWT_TOKEN>

There are two authentication middleware levels:

Regular authentication

Used for normal authenticated users.

authMiddleware

System-user authentication

Used for privileged system operations.

authSystemUserMiddleware

System-user middleware verifies both the JWT and the user's systemUser flag.


Database Transactions

Financial operations use MongoDB sessions and transactions to keep related database changes atomic.

A normal transfer consists of multiple writes:

Transaction record
        +
Debit ledger entry
        +
Credit ledger entry

These operations are committed together.

If any operation fails:

MongoDB Transaction
       │
       ├── Transaction ❌
       ├── Debit       ❌
       └── Credit      ❌

The transaction is aborted so that partial ledger records are not committed.


Error Handling

The API returns appropriate HTTP status codes for common failures.

Examples:

Status Meaning
200 Successful request
201 Resource created
400 Invalid request / insufficient balance
401 Authentication required or invalid
403 Insufficient privileges
404 Resource not found
422 User already exists
500 Transaction/server failure

Example error:

{
  "message": "Insufficient balance in from account"
}

Security Considerations

The project includes several security mechanisms:

  • Password hashing with bcrypt
  • JWT authentication
  • HTTP-only authentication cookies
  • JWT expiration
  • Token blacklisting on logout
  • System-user authorization
  • User-specific account access checks
  • Idempotency protection for transactions
  • Immutable ledger records
  • MongoDB atomic transactions

For production deployment, additional protections such as HTTPS, secure cookie configuration, rate limiting, input validation, logging, monitoring, and secret management should be added.


Available Scripts

Command Description
npm install Install dependencies
npm run dev Start development server with Nodemon
npm start Start the production server

Example Transaction

Suppose:

Account A balance = ₹1,000
Account B balance = ₹500

A transfer of ₹300 from A to B produces:

Account A
DEBIT  ₹300
Balance = ₹700

Account B
CREDIT ₹300
Balance = ₹800

The ledger records remain available as the source of truth for calculating balances.


Future Improvements

Potential improvements include:

  • Request validation using a schema validation library
  • Centralized error-handling middleware
  • Transaction history endpoints
  • Pagination for accounts and transactions
  • Account creation with configurable currency
  • Account ownership rules for transfers
  • Better transaction reversal workflows
  • Rate limiting
  • Structured application logging
  • API documentation with OpenAPI/Swagger
  • Automated unit and integration tests
  • Docker support
  • Production-ready security configuration
  • Proper role/permission management
  • Audit logging
  • Improved concurrency handling around balance checks

License

This project currently uses the ISC license as specified in package.json.


About

A backend ledger system demonstrating double-entry accounting, atomic transactions, idempotency, JWT authentication, and immutable financial records.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages