<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Akram Ashraf</title>
    <description>The latest articles on DEV Community by Akram Ashraf (@akram_ashraf01).</description>
    <link>https://dev.to/akram_ashraf01</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3830922%2F5a71ac50-c63c-41d5-a001-f074a9be8b4a.png</url>
      <title>DEV Community: Akram Ashraf</title>
      <link>https://dev.to/akram_ashraf01</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/akram_ashraf01"/>
    <language>en</language>
    <item>
      <title>Building a Double-Entry Accounting Engine in Node.js (Open Source — ledgerstack-core)</title>
      <dc:creator>Akram Ashraf</dc:creator>
      <pubDate>Wed, 18 Mar 2026 08:01:59 +0000</pubDate>
      <link>https://dev.to/akram_ashraf01/building-a-double-entry-accounting-engine-in-nodejs-open-source-ledgerstack-core-52m</link>
      <guid>https://dev.to/akram_ashraf01/building-a-double-entry-accounting-engine-in-nodejs-open-source-ledgerstack-core-52m</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhjrzt75ba5oc69ojz7tk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhjrzt75ba5oc69ojz7tk.png" alt=" "&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Most backend developers eventually face this problem:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I need accounting inside my app, but I don’t want to build a full ERP.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Whether you are building:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;SaaS platform&lt;/li&gt;
&lt;li&gt;Billing system&lt;/li&gt;
&lt;li&gt;ERP&lt;/li&gt;
&lt;li&gt;POS&lt;/li&gt;
&lt;li&gt;Fintech app&lt;/li&gt;
&lt;li&gt;Multi-tenant system&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;you will need:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Double-entry accounting&lt;/li&gt;
&lt;li&gt;Trial balance&lt;/li&gt;
&lt;li&gt;Balance sheet&lt;/li&gt;
&lt;li&gt;Ledger&lt;/li&gt;
&lt;li&gt;Financial year&lt;/li&gt;
&lt;li&gt;Voucher system&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There are many accounting softwares, but very few developer-friendly accounting engines for Node.js.&lt;/p&gt;

&lt;p&gt;So I built one.&lt;/p&gt;




&lt;h2&gt;
  
  
  🚀 Introducing ledgerstack-core
&lt;/h2&gt;

&lt;p&gt;npm:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.npmjs.com/package/ledgerstack-core" rel="noopener noreferrer"&gt;https://www.npmjs.com/package/ledgerstack-core&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;ledgerstack-core&lt;/code&gt; is a double-entry accounting engine for Node.js designed for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Multi-tenant SaaS apps&lt;/li&gt;
&lt;li&gt;High performance systems&lt;/li&gt;
&lt;li&gt;SQL databases&lt;/li&gt;
&lt;li&gt;Worker-based processing&lt;/li&gt;
&lt;li&gt;Cache-based reports&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Supported DBs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;PostgreSQL&lt;/li&gt;
&lt;li&gt;MySQL&lt;/li&gt;
&lt;li&gt;SQLite&lt;/li&gt;
&lt;li&gt;MSSQL&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Why not use existing libraries?
&lt;/h2&gt;

&lt;p&gt;Most accounting libraries are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Too simple (no balance sheet / no year / no groups)&lt;/li&gt;
&lt;li&gt;Too complex (full ERP)&lt;/li&gt;
&lt;li&gt;Not multi-tenant&lt;/li&gt;
&lt;li&gt;Slow with large data&lt;/li&gt;
&lt;li&gt;Not DB-agnostic&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I needed something like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;initAccounts&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="nf"&gt;createVoucher&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="nf"&gt;getTrialBalance&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="nf"&gt;processJobs&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;but production-ready.&lt;/p&gt;

&lt;p&gt;So I built ledgerstack-core.&lt;/p&gt;




&lt;h2&gt;
  
  
  Features
&lt;/h2&gt;

&lt;h3&gt;
  
  
  ✅ Double-entry accounting
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Debit = Credit enforced&lt;/li&gt;
&lt;li&gt;Voucher-based system&lt;/li&gt;
&lt;li&gt;Journal / Payment / Receipt / Sales / Purchase&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  ✅ Multi-tenant ready
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Tenant&lt;/li&gt;
&lt;li&gt;Company&lt;/li&gt;
&lt;li&gt;Financial year&lt;/li&gt;
&lt;li&gt;Period lock&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  ✅ Cache-based performance
&lt;/h3&gt;

&lt;p&gt;Reports use cache tables instead of aggregates.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;account_balance_cache
account_daily_balance
balance_jobs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This keeps reports fast even with large data.&lt;/p&gt;

&lt;h3&gt;
  
  
  ✅ Worker-based recalculation
&lt;/h3&gt;

&lt;p&gt;Voucher change → queue job → worker updates cache&lt;/p&gt;

&lt;p&gt;No slow API calls.&lt;/p&gt;

&lt;h3&gt;
  
  
  ✅ Default groups (like Tally)
&lt;/h3&gt;

&lt;p&gt;Seeded automatically:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Assets&lt;/li&gt;
&lt;li&gt;Liabilities&lt;/li&gt;
&lt;li&gt;Income&lt;/li&gt;
&lt;li&gt;Expenses&lt;/li&gt;
&lt;li&gt;Equity&lt;/li&gt;
&lt;li&gt;Cash&lt;/li&gt;
&lt;li&gt;Bank&lt;/li&gt;
&lt;li&gt;Profit &amp;amp; Loss&lt;/li&gt;
&lt;li&gt;Suspense&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  ✅ Built-in reports
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Trial Balance&lt;/li&gt;
&lt;li&gt;Ledger&lt;/li&gt;
&lt;li&gt;Ledger with running balance&lt;/li&gt;
&lt;li&gt;Balance Sheet&lt;/li&gt;
&lt;li&gt;Profit &amp;amp; Loss&lt;/li&gt;
&lt;li&gt;Day Book&lt;/li&gt;
&lt;li&gt;Cash Book&lt;/li&gt;
&lt;li&gt;Group Summary&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Architecture
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;App
  ↓
ledgerstack-core
  ↓
adapter
  ↓
database
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Modules:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;init&lt;/li&gt;
&lt;li&gt;adapter&lt;/li&gt;
&lt;li&gt;accounts&lt;/li&gt;
&lt;li&gt;vouchers&lt;/li&gt;
&lt;li&gt;balance&lt;/li&gt;
&lt;li&gt;cache&lt;/li&gt;
&lt;li&gt;reports&lt;/li&gt;
&lt;li&gt;worker&lt;/li&gt;
&lt;li&gt;audit&lt;/li&gt;
&lt;li&gt;currency&lt;/li&gt;
&lt;li&gt;cost center&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Example Usage
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;initAccounts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;migrate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;createAccount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;createVoucher&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;getTrialBalance&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;processJobs&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ledgerstack-core&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Init&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;initAccounts&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="nx"&gt;adapter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;tenant&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;worker&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;migrate&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create account&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;createAccount&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Cash&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;group&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Cash-in-Hand&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create voucher&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;createVoucher&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;date&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="na"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;account&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Cash&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;debit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;account&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Sales&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;credit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run worker&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;processJobs&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Get report&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;tb&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;getTrialBalance&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Why cache-based accounting?
&lt;/h2&gt;

&lt;p&gt;Most systems do:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This becomes slow.&lt;/p&gt;

&lt;p&gt;ledgerstack uses:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;balance_cache
daily_cache
worker_jobs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So reports stay fast.&lt;/p&gt;




&lt;h2&gt;
  
  
  Who is this for?
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Backend developers&lt;/li&gt;
&lt;li&gt;SaaS builders&lt;/li&gt;
&lt;li&gt;ERP developers&lt;/li&gt;
&lt;li&gt;Fintech apps&lt;/li&gt;
&lt;li&gt;Internal tools&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Not for UI users.&lt;/p&gt;




&lt;h2&gt;
  
  
  npm
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://www.npmjs.com/package/ledgerstack-core" rel="noopener noreferrer"&gt;https://www.npmjs.com/package/ledgerstack-core&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  GitHub
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/akram-ashraf/ledgerstack-core" rel="noopener noreferrer"&gt;https://github.com/akram-ashraf/ledgerstack-core&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Future plans
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;inventory module&lt;/li&gt;
&lt;li&gt;tax module&lt;/li&gt;
&lt;li&gt;report builder&lt;/li&gt;
&lt;li&gt;plugin system&lt;/li&gt;
&lt;li&gt;auto docs from TS&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Feedback welcome
&lt;/h2&gt;

&lt;p&gt;If you are building accounting in Node.js, try it and share feedback.&lt;br&gt;
This project is still evolving.&lt;/p&gt;

</description>
      <category>node</category>
      <category>accounting</category>
      <category>npm</category>
      <category>typescript</category>
    </item>
  </channel>
</rss>
