Back to Blog

Building Catspense - From Idea to Architecture

by Zaky Syihab Hatmoko (teszerrakt)
catspensearchitectureopen-sourcemonorepoplugin-systemexpense-tracker

One day after my first blog post, I'm already starting the next project. This is Catspense - an open-source expense tracker I'm building with community contribution in mind.

Not just another expense tracker

There are plenty of expense trackers. Here's what I'm doing differently:

Plugin-first architecture. Instead of trying to support every bank myself, I'm building a system where developers can create email parser plugins for their own banks. Parsers for Jenius, Mandiri, Wise, and eventually banks from other countries - all community-contributed.

Built for open source. This isn't just for me. I want it to be a platform where developers can contribute parsers and benefit from each other's work.

International by design. Starting with Indonesian banks because that's what I use, but architected to handle any currency and banking system.

Why "Catspense"?

Cat + Expense. Memorable enough, not too serious. The cat theme stays subtle.

Tech Stack Decisions

Here's what I've settled on and why.

Monorepo Architecture

  • pnpm workspaces for package management
  • Turborepo for build orchestration
  • Clean separation between apps, packages, and plugins

Frontend Stack

  • Next.js 15 with App Router for the web app
  • Tamagui for the design system (planning mobile later)
  • TypeScript throughout
  • Tailwind CSS for utility-first styling

Backend & Data

  • tRPC for type-safe APIs
  • Supabase for database and auth
  • Zod for validation schemas

The Plugin System

This is the part that matters most. Here's how it works:

interface EmailParser {
  name: string;
  version: string;
  parseEmail: (emailContent: string) => Transaction[];
  validateEmail: (emailContent: string) => boolean;
}

Each plugin is a self-contained package that knows how to:

  1. Identify if an email is from their target bank
  2. Extract transaction data from that email
  3. Return standardized transaction objects

Planned Official Plugins

  • @catspense/plugin-jenius - Jenius SMBC email parser (my primary bank)
  • @catspense/plugin-mandiri - Mandiri bank emails (my secondary bank)
  • @catspense/plugin-template - Boilerplate for new parsers

I'm starting with these two because they're the banks I actually use. Once the system works with real data, I'll open source it and let others add parsers for their own banks.

Repository Structure

catspense/
├── apps/
│   ├── web/                 # Next.js web app
│   └── mobile/              # React Native (future)
├── packages/
│   ├── ui/                  # Tamagui design system
│   ├── shared/              # Business logic
│   ├── database/            # Supabase schemas
│   ├── plugin-system/       # Core plugin architecture
│   ├── plugins/             # Official email parsers
│   └── config/              # Shared configurations
├── docs/                    # Documentation
└── [standard files]

Multi-Currency Support

Since this is meant for international use, multi-currency is built into the foundation:

  • IDR (Indonesian Rupiah) - my primary currency
  • USD, SGD, MYR - common regional currencies
  • Extensible system for adding more
  • Proper formatting and display for each currency

What's Next?

Today I'm focusing on:

  1. Setting up the monorepo structure - Getting pnpm workspaces and Turborepo configured
  2. Building the plugin system foundation - Core interfaces and registration system
  3. Creating the design system - Tamagui setup with the Zen-inspired theme
  4. Initial web app scaffold - Basic Next.js setup with routing

Tomorrow:

  • First email parser plugin (probably Jenius since that's my main bank)
  • Transaction dashboard UI
  • Plugin management interface

Tech Stack Deep Dive

Here's the reasoning behind each choice.

Monorepo with pnpm + Turborepo

Why monorepo? With a plugin architecture, you need to manage multiple related packages that share dependencies and types. A monorepo keeps everything in sync.

Why pnpm over npm/yarn? Faster installs, better disk space efficiency, stricter dependency resolution. For an open source project, fast setup matters.

Why Turborepo? Incremental builds and smart caching. When the core plugin system changes, only affected packages rebuild.

Next.js 15 with App Router

Why Next.js? Server-side rendering for better SEO matters for an open source project's discoverability. The App Router gives a clean way to organize the dashboard, plugin management, and docs pages.

Why not Vite/React SPA? Marketing pages and docs need to load fast and be SEO-friendly. tRPC also integrates well with Next.js API routes.

Tamagui for Design System

Why Tamagui over Tailwind components? I'm planning mobile later. Tamagui gives React Native compatibility out of the box, so components can be shared between web and mobile.

Why not just Tailwind? Tailwind is great for styling, but I want a more structured design system with consistent theming and built-in dark mode. Tamagui gives that.

tRPC + Supabase

Why tRPC? End-to-end type safety from database to frontend. When a schema changes, TypeScript catches breaking changes across the stack.

Why Supabase over a custom backend? I want to focus on the plugin system, not building auth and database infrastructure from scratch. Supabase gives me PostgreSQL, real-time subscriptions, and auth without that overhead.

Why not Prisma + custom backend? Speed to MVP. Supabase's auto-generated types and real-time features let me build faster and focus on what actually differentiates Catspense.

TypeScript Everywhere

With a plugin system, type safety is critical. Plugin developers need clear interfaces, and the core system needs confidence that plugins won't break things.

Open Source Strategy

The repository is private while I build the initial MVP. But everything is designed for open source from day one - architecture, docs structure, plugin system.

Once I have a working proof of concept with my two bank parsers functioning reliably, I'll open source the whole project with:

  • Clear contribution guidelines
  • Plugin development documentation
  • Issue templates for bug reports and feature requests
  • Community guidelines

Why wait for MVP? I want contributors to see a working system they can build on, not empty folders and TODO comments. First impressions matter in open source.

Why this approach

Building Catspense as a plugin-based open source system means:

  • Collective value: Every parser someone contributes helps users everywhere
  • Local expertise: Developers who use specific banks can build the best parsers for them
  • Shared maintenance: The community can maintain and improve parsers over time
  • Good learning project: Monorepo architecture, plugin systems, open source maintainership

Following Along

I'll document everything as I build - what works, what doesn't, and the inevitable configuration problems. The repository will be public once the core plugin system works and at least one parser is functional.

Next post will probably be about the monorepo setup and getting the first plugin working. Or about the configuration headaches that come with it.


Post #2 in my development journey. GitHub link coming once the POC is ready.