Abacus: Expenses Bookkeeping Tool

About a year ago, I started a side project for fun as well as the need to keep track of my daily expenses in my own way. The outcome, Abacus, is an expenses bookkeeping web service (still under active development, but in good shape), which I have been using for months.

Abacus uses Django as the website backend engine, providing webpages for users to record, view and manage their daily expenses transactions in groups. Additionally, Abacus also has a Telegram bot interface to accomplish the same bookkeeping actions in Telegram chat. Abacus is meant to be deployed on a home PC as a self-hosted web service for personal use.

Motivations

I was using Splitwise to track my daily expenses and those were shared with others. Splitwise was great until a year ago it limits every non-premium user to add at most 4 transactions per day. The 3rd and 4th of the 4 “free” transactions actually require users to watch a 10-second advertisement first. My old phone with an old version of Splitwise was able to exceed the limit until several months later they patched up the loophole eventually.

Someone else actually made a Reddit post asking for self-hosted alternatives to Splitwise due to the same reason. I am glad I did not see the post at that time, otherwise the discussion will probably drive me to use CoSpend or IHateMoney.

The annoying limits in Splitwise just pulled the trigger, but I also wanted to achieve some other goals via this project:

  • Most importantly, practice coding and have fun
  • Stop exposing the privacy (i.e., daily expenses) to third-party
  • Build customized features (e.g., divide long-term expenses by period)

Development

The project has been through 4 phases so far:

The following sub-sections will first introduce Phase 1 and 4 together as website design, then Phase 2 and 3 together as Telegram bot design.

Website

Although Splitwise is a phone App, I started building my tool as a website. This is not only because I had more experience in web development than in app development, but also a website should have higher return of interest than an App: I can access the website in any browser from my phones, tablets and computers, while App cannot even easily work for both Andriod phones and iPhones.

I choose Django as the backend engine, given it is a Python package, and has database, authentication, rendering working out of box. It is really easy to bring up the basic website I want with Django. Users could sign in with username and password, then on the home page a list of groups will listed, click on one to jump to the group page where all transactions in the group is listed. There are buttons to create new groups and create new transactions. The page to create a new transaction is rendered from a model form of the Transaction model. And I reuse the same page template to show the details of an existing transaction, meanwhile allowing users to modify the transaction.

Such a website covers the basic functionality of Splitwise and worked fine until recently. We recorded more than 500 transactions since the website is up and running last year. It is getting slower to render the group page at the backend then load the page with full list of transactions in browser. Also, with a long list, it becomes challenging to find the transactions. I decided to address the issues before hitting prohibited experience.

The approach I took is asynchronous page loading and dynamically adjusting the page with javascript. After the enhancement, the backend will first return the skeleton of the group page, with no transactions at all. However, a set of javascript functions takes care of the transaction list. One of the function queryTransactions() will send a POST request to the backend querying the transactions. Other functions are the callback functions that add transactions on the page once the query results is received. I also added the paging buttons at the bottom of the group page, so up to 50 transactions will be displayed at once, but users could go through all the transactions by going through the pages. A search box is added to filter transactions with certain keywords, as well as an advanced serach panel where users could further refine the results by specifying date, total, payer and other properties of the transactions. The search invokes the same queryTransactions() function with just different fields in the POST request. The paging and search were actually implemented for Telegram bot interface first.

Another enhancement to the group transactions page is a switch button for mobile view, which would not overflow the narrow phone screen via bootstrap responsive widgets. The table view (before switching to mobile view) also uses bootstrap, but due to the number of columns, it still fits computer monitors better. A javascript function is used to automatically detect if mobile device is requesting and initialize the switch button accordingly.

Analyzing the trend of spending is one of the starting points why I want to record the daily expenses. Since we get the transactions query results in javascript, it becomes straight forward to use Observable Plot to visualize the data. As of now, I got the bar chart that groups transactions count or total by month or week. I am planning to add more charts in the future, such as a zoomable sunburst chart to show transactions composition of different categories (yes, Abacus allows users to mark transactions with hierarchical categories).

Chart as well as search are premium features on Splitwise, and now I get them for free :)

Telegram Bot

The desktop PC is the only resource I plan to use. Paying the fees to rent a cloud VM and a domain name is not much better than paying the premium of Splitwise. Given this restriction, it was just a local website only accessible within the LAN network at home as the project started. Apperantly, this is very inconvienient that I need to remember to collect all the receipts and record the expenses once reach home before I forget them. And only at home, I can lookup how much I have spent or owe others. The following diagram shows the architecture:

 +---------------------------------------------+
 | LAN           +---------------------------+ |
 |               | Home PC                   | | Legend:
 |  Device <-B-> |  abacus_web <=> sqlite DB | |
 |               +---------------------------+ | <-B-> via Browser
 +---------------------------------------------+

Luckily, I figured out a solution to address the shortcoming not long after. Telegram provides the Bot APIs that allows the bots program written by developers to communicate with users in Telegram chat. There has been a well-developed Python package, python-telegram-bot, to make writing Telegram bot program fairly easy. Therefore, I wrote my own Telegram bot program, abacus_bot running on the same PC where the website, abacus_web is deployed. The Telegram bot Python script accesses the same sqlite database via Django modules. I added a telegram_id field to each user’s record, and once the Telegram user binds to their website account with username and password, the telegram_id field will be populated. For the future converstaions in the Telegram chat, the bot program will lookup the database with telegram_id to figure out which user it is talking to. This is how Abacus architecture expends to serve devices in WAN:

 +---------------------------------------------+
 | LAN           +---------------------------+ |
 |               | Home PC                   | |
 |  Device <-B-> |  abacus_web <=> sqlite DB | | Legend:
 |               |         /                 | |
 |  +----------> |  abacus_bot               | | <-B-> via Browser
 |  |            +---------------------------+ | <-T-> via Telegram
 +--|------------------------------------------+
    |    Telegram
    +->   Server  <-T-> Device in WAN

Telegram Bot API provides rich UI widgets for users to efficiently perform all kinds of actions. For example, I registered different commands: /list_transaction to list transactions; /new_transaction to start the conversation to add a transaction; /summary to summarize the transactions, showing balance. Within the converstaion (a concept of Telegram bot to have finite state machine in context), the bot could make a list of group members presented as Inline Buttons, so that users just need one click on the button to select the payer of the transaction. As the conversation goes, the bot could also preset Reply Keybord with possible replies that user frequently uses to reduce the effort to type.

Nevertheless, Telegram chat is still mainly text-based. The interaction is sufficient for simple operations, but sometime still requires quite a bit typing. For example, to search the grocery transactions for last month, user needs to send /list_transaction <category:grocery> <date:last month>. And if the user want to see how the total expenses of the past three weeks changes, each month needs to be queried separately like: /summary last week, /summary 02/03/2025-02/09/2025, and /summary 01/27/2025-02/02/2025. Whereas on webpage, date could be picked from calender, and there is already bar charts to visualize the trend.

During the development of Telegram bot, I learned that Telegram has mini-app which is essentially launching web pages from the chat, and view as well as interact with the web page in an interal browser built-in Telegram. However, it requires a public accessible URL and the protocal must be HTTPS. Later, I heard about tunneling service (e.g, cpolar I used) that could map certain port of the home PC to a public domain name adding SSL level as well. This is achieved by running a client to establish the connection between your home PC and the tunneling proxy server first, and the request to the proxy server will be forwarded to your home PC. The only shortcoming is you will likely need to pay to reserve a domain name. I go with the free plan, and the domain name assigned to me changes randomly every 24 hours. However, I let the Telegram bot parse the client log to find the domain name currently assigned and return the URL to users or created the button to launch the mini-app directly (then the random URL is hidden). Here is the latest Abacus architecture with tunneling:

 +---------------------------------------------+
 | LAN           +---------------------------+ |
 |               | Home PC                   | |
 |  Device <-B-> |  abacus_web <=> sqlite DB | |
 |               |         /   \             | |
 |  +----------> |  abacus_bot   cpolar      | |
 |  |            +------------------|--------+ | Legend:
 +--|-------------------------------|----------+
    |                   cpolar Server <---+      <-B-> via Browser  
    |    Telegram                         |      <-T-> via Telegram 
    +->   Server  <-T-> Device in WAN <-B-+

Miscellaneous

Other than the website and Telegram bot designs I described above, I also setup a cron job to periodically backup the database in case something bad happens and corrupts the file.

The Django backend is the application engine, so I actually configured Apache httpd deamon running on the PC to bridge client and the Django application as suggested.

Closing

The experience of using Abacus is positive and satisfying as of now, and I am excided to see more features to be added later. I am so happy that I finally get something built to directly address a decent need in my daily life. During the project, I has learnt quite a bit, practiced a lot.

Unfortunately, there would be some process I need to go through before I can open-source the code, and I am not planning to try right now. Therefore, you will find Abacus repo is not publically accessible. But if you are really interested in Abacus, please contact me.

Credits

  • Django - The backend engine used in the project.
  • Telegram bot - Great API that brings Abacus to a higher level.
  • python-telegram-bot - Wonderful Python package making it really easy to program Telegram bot.
  • cpolar - Thanks for the free tunneling service that makes it possible to access my website from outside world.
  • Observable Plot - Nice visualize tool.
  • bootstrap - The web development bundle used to beautify my web page.
  • jQuery - The javascriopt package used in the project, specifically thanks for the datetime picker UI widget.
  • detectmobilebrowsers.com - Thanks for the function to detect mobile device
  • Many thanks to those who share and discuss on the Internet so that I come across the solutions I search for.