CLI + Web · TypeScript & Node.js

Expense Tracker CLI

Expense Tracker CLI
2 Interfaces, 1 Core
Result<T, E> Error Handling

The Problem

A personal project built to track day-to-day expenses without pulling in a database or a heavyweight app, and to use the project as a deliberate exercise in explicit error handling: instead of the usual throw/catch approach, every operation in the codebase says exactly whether it succeeded or failed, and why, so a failure can never pass through silently.

My Role

Sole developer, owning the project end-to-end, from the core data model through both the CLI and the web interface built on top of it.

Technical Decisions

Result<T, E> instead of exceptions

The whole codebase is built around a generic Result<T, E> type (src/types.ts) instead of throwing exceptions, every operation (storage read/write, add, delete) returns { ok: true, value } or { ok: false, error }, forcing callers to explicitly handle failure. Storage errors are also typed as discriminated unions (read_failed, parse_failed, write_failed, etc.), so the compiler catches an unhandled failure case instead of it surfacing as a runtime crash.

One core, two interfaces

The same logic is exposed two ways rather than being duplicated: a CLI (src/index.ts) for add, list, and delete commands run directly from the terminal, and an Express web server (src/server.ts) that exposes the identical operations as a REST API (GET/POST /api/expenses, DELETE /api/expenses/:id) while serving a static frontend. Keeping one shared core meant the business logic only had to be correct in one place.

Flat-file JSON storage

Expenses are stored as plain JSON in data/expenses.json (id, description, amount, date) instead of a database. For a single-user, local tool this removed an entire layer of setup and dependencies without giving up the type-safety goals of the project, since reads and writes still go through the same Result-returning storage functions.

Project Information

  • Category: CLI + Web Tool (TypeScript, Node.js)
  • Type: Personal project
  • Role: Solo developer
  • Timeline: 2026
Tech Stack
TypeScript Node.js Express Result<T, E>