Engineering Reliable Double-Entry Accounting Systems in Modern Web Apps
Bridging software engineering and accounting principles: immutable ledgers, ACID transaction guarantees, debit/credit balancing, and audit trails.
As an Accounting Information Systems student and Fullstack Developer, I frequently see financial applications rely on simplistic balance = balance + amount mutations. In financial software, this is dangerous: balance is a derived calculation, not a mutable state.
The Golden Rule of Double-Entry Bookkeeping
Every financial transaction consists of at least two journal entries: $$\sum \text{Debits} = \sum \text{Credits}$$
An immutable ledger guarantees that money is never created or destroyed out of thin air.
Schema Architecture in PostgreSQL
CREATE TABLE accounts (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
account_number VARCHAR(32) UNIQUE NOT NULL,
name VARCHAR(128) NOT NULL,
type VARCHAR(32) NOT NULL CHECK (type IN ('ASSET', 'LIABILITY', 'EQUITY', 'REVENUE', 'EXPENSE')),
currency VARCHAR(3) DEFAULT 'IDR'
);
CREATE TABLE transactions (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
reference_id VARCHAR(64) UNIQUE,
description TEXT NOT NULL,
posted_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
CREATE TABLE journal_entries (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
transaction_id UUID REFERENCES transactions(id) ON DELETE RESTRICT,
account_id UUID REFERENCES accounts(id),
amount NUMERIC(18, 4) NOT NULL,
direction VARCHAR(6) NOT NULL CHECK (direction IN ('DEBIT', 'CREDIT')),
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);Enforcing Transaction Balance with Database Triggers
CREATE OR REPLACE FUNCTION verify_transaction_balance()
RETURNS TRIGGER AS $$
DECLARE
total_debit NUMERIC(18, 4);
total_credit NUMERIC(18, 4);
BEGIN
SELECT
COALESCE(SUM(CASE WHEN direction = 'DEBIT' THEN amount ELSE 0 END), 0),
COALESCE(SUM(CASE WHEN direction = 'CREDIT' THEN amount ELSE 0 END), 0)
INTO total_debit, total_credit
FROM journal_entries
WHERE transaction_id = NEW.transaction_id;
IF total_debit <> total_credit THEN
RAISE EXCEPTION 'Transaction unbalanced! Total Debits: %, Total Credits: %', total_debit, total_credit;
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;This architecture guarantees mathematical precision, zero balance corruption, and 100% compliance with audit standards.