Skip to content

Repository files navigation

Pocket Pixel — Expense Tracker

Level up your finances. Track every coin, quest every habit.

CI/CD MIT License TypeScript Next.js Express SQLite PRs Welcome Stars

Banner

What is Pocket Pixel?

Pocket Pixel is a gamified personal finance tracker built for people who want to make budgeting actually fun. Inspired by retro RPG aesthetics, it wraps your income and expenses in a pixel-art UI where categories become Vaults, habits become Quests, and every transaction is part of your financial adventure.

  • Vaults — organize spending into themed buckets (food, rent, subscriptions, anything)
  • Recurring Quests — automate repeating transactions on daily, weekly, monthly, or yearly schedules
  • Tags — label transactions with custom icons and colors for granular analytics
  • Analytics — monthly and yearly breakdowns, tag-based insights
  • Profiles — pick your avatar and make it your own
  • Transaction auto-import — watch a Gmail label (bank/card alerts) and turn matching emails into transactions automatically, no manual entry
  • AI-Assisted Parsing — a review queue for emails that need a closer look; parsed client-side in your browser using your own OpenRouter API key
  • Wizard Assistant — an in-character chat guide that reads your vaults/transactions and gives spending advice, also powered by your own AI key
  • Push Notifications — get pinged when a new pending expense is waiting for review

🔐 User-managed, not shared

Pocket Pixel doesn't ship with a shared Google OAuth client or a shared AI API key. Every user connects their own Gmail OAuth client and brings their own OpenRouter key:

  • Gmail OAuth credentials are encrypted at rest on the server (AES-256-GCM).
  • The OpenRouter key is encrypted client-side (DEK-based, end-to-end) before it ever leaves your browser — the server only ever stores opaque ciphertext and never sees your plaintext key.
  • No email body is parsed or stored server-side. Matching emails are held as a pointer (message id + vault) in a pending queue; the actual parsing happens in your browser with your own key.

See documentation/gmail-integration.md for the full setup (GCP project, Pub/Sub, OAuth client, per-user onboarding).


Project Structure

pocket_pixel/
├── packages/
│   ├── api/          # Express REST API + TypeORM entities
│   │   └── src/
│   │       ├── entities/     # User, Expense, Vault, Tag, TransactionTag, VaultGmailWatcher, PendingGmailExpense
│   │       ├── routes/       # auth, users, transactions, vaults, tags, recurring, analytics,
│   │       │                 # vault-watchers, pending-expenses, ai-credentials, oauth (Google/Gmail)
│   │       ├── services/     # gmail.service (watch + webhook), pending-gmail-expense, user-ai-credential,
│   │       │                 # user-oauth-credential (encrypted Google tokens), ...
│   │       ├── middleware/   # JWT auth, error handling
│   │       └── scheduler/   # node-cron recurring job manager + daily Gmail watch renewal
│   │
│   ├── ui/           # Next.js frontend
│   │   └── src/
│   │       ├── app/          # Dashboard, Profile, Stats, Auth pages
│   │       ├── components/   # Modals, AppBar, Nav, UI primitives
│   │       └── lib/          # API clients, helpers, icon mapper
│   │
│   └── shared/       # Shared TypeScript types/interfaces
│
├── ecosystem.config.js   # PM2 production config
├── tsconfig.base.json
└── package.json          # Workspace root

Getting Started

Clone the repo

git clone git@github.com:ali-ahnaf/pocket_pixel.git
cd pocket_pixel
npm install
npm run build:shared # builds shared dependencies
npm run migration:run # creates/updates the .sql file

Create .env files

Create .env files for both the api and the ui

  • Copy .env.example to .env for both the api and the ui
  • Fill the .env files with the appropriate values (or keep the defaults for local development)

Development

Run the API and UI in separate terminals:

# Terminal 1 — API (http://localhost:4000)
npm run dev:api

# Terminal 2 — UI (http://localhost:3000)
npm run dev:ui

Test

# API tests
npm run test:api

# UI E2E tests
npm run test:e2e

Production Build

# Build shared → UI → API in order
npm run build:prod

# run migrations and saves the file in /var/www/pocket_pixel
npm run migration:run-prod

# Start the server (API serves the compiled UI)
pm2 start ecosystem.config.js
# → http://localhost:4000

API Reference

All endpoints are prefixed with /api. Protected routes require an Authorization: Bearer <token> header.


Database

SQLite database managed via TypeORM with migrations.

Run migrations:

npm run migration:run        # Apply pending migrations
npm run migration:generate   # Generate migration from entity changes
npm run migration:revert     # Roll back the last migration

Database Backups (Cloudflare R2)

The API can snapshot the SQLite database and upload it to Cloudflare R2 (an S3-compatible object store) every 12 hours. It's off by default — flip ENABLE_BACKUP=true in the API .env to turn it on. Backups use SQLite's online backup API, so snapshots stay consistent even while the app is writing.

Each run uploads an object named pocket_pixel/pocket_pixel-<timestamp>.sqlite to your bucket.

Why R2? The free tier includes 10 GB storage and, unlike most clouds, zero egress fees — you can pull your backups down for free. That's plenty for a SQLite file.

1. Create a Cloudflare account & enable R2

  1. Sign up (or log in) at dash.cloudflare.com — the account is free.
  2. In the sidebar open R2 Object Storage and click Enable R2. Cloudflare asks for a payment method to verify identity, but you are not charged while you stay within the free tier limits.

2. Create a bucket

  1. Go to R2 → Create bucket.
  2. Name it (e.g. pocket-pixel-backups) and create it. The location hint can stay on Automatic.
  3. Remember this name — it's your R2_BUCKET.

3. Create an API token

  1. On the R2 overview page click Manage R2 API Tokens → Create API Token.
  2. Permission: Object Read & Write.
  3. Scope it to the bucket you just created (recommended), then Create.
  4. Copy the Access Key ID and Secret Access Key — the secret is shown only once.

4. Find your Account ID

Your Account ID is on the R2 overview page (and in the S3 endpoint Cloudflare shows: https://<ACCOUNT_ID>.r2.cloudflarestorage.com). Copy it.

5. Configure the API .env

Add these to packages/api/.env (see packages/api/.env.example):

ENABLE_BACKUP=true
R2_ACCOUNT_ID=your-account-id
R2_ACCESS_KEY_ID=your-access-key-id
R2_SECRET_ACCESS_KEY=your-secret-access-key
R2_BUCKET=pocket-pixel-backups

Restart the API to pick them up (pm2 restart ecosystem.config.js in production, or restart npm run dev:api locally). On boot you should see Database backup scheduled every 12 hours to R2 in the logs. If R2 credentials are missing while ENABLE_BACKUP=true, the scheduler logs a warning and stays idle instead of crashing.

Restoring a backup

  1. Download the desired .sqlite object from your R2 bucket (Cloudflare dashboard or any S3 client).
  2. Stop the API.
  3. Replace the live database file (/var/www/pocket_pixel/pocket_pixel.sqlite in production, or packages/api/pocket_pixel.sqlite locally) with the downloaded file.
  4. Start the API again.

Features In Depth

Vaults

Organize your money into custom buckets — think of them as tagged envelopes. Each vault has a name, icon (from Lucide), background color, and can be marked as your default. Transactions without a vault fall into the default one.

Recurring Quests

Set a transaction to auto-repeat on a schedule (daily / weekly / monthly / yearly). The API scheduler restores all active quests on startup using node-cron, so nothing gets missed between restarts.

Analytics

Three views to understand your spending:

  • Tag breakdown — which labels are eating your budget
  • Monthly report — income vs. expenses by month
  • Yearly report — long-term trend across all months

Gmail Auto-Import & AI Parsing

Connect Gmail, pick a label (e.g. a filter that tags bank alert emails), and point it at a vault. Google pushes new mail to the API via Pub/Sub in real time:

  1. A confident, rule-based match is recorded straight as a transaction.
  2. Anything less certain is enqueued in a pending review queue instead — only the Gmail message id, vault, and a guidance hint are stored, never the email body.
  3. You get a push notification, open the pending item in the UI, and it's parsed client-side, in your browser, using your own OpenRouter API key.
  4. Confirm and it becomes a transaction; dismiss and it's cleared from the queue.

The Wizard Assistant chat (Settings → AI) uses the same client-side OpenRouter key to answer questions about your spending — nothing is sent to Pocket Pixel's own servers for either feature.

Full setup (GCP project, Pub/Sub topic/subscription, OAuth client, per-user onboarding): documentation/gmail-integration.md.


Contributing

Contributions are what make open source awesome. All skill levels welcome — whether it's fixing a typo, adding a new feature, or improving the docs.

How to Contribute

  1. Fork the repository
  2. Create a feature branch
    git checkout -b feat/your-feature-name
  3. Make your changes — keep commits focused and descriptive
  4. Test your changes locally (both dev:api and dev:ui)
  5. Push your branch and open a Pull Request

Code Style

  • Prettier is configured
  • TypeScript strict mode is enforced
  • Keep components small and single-purpose
  • Name things clearly — no abbreviations unless obvious
  • Do not make changes in the file that are not relevant to the task at hand.

Reporting Bugs

Open an issue with:

  • What you expected vs. what happened
  • Steps to reproduce
  • Your OS and Node.js version

⚔️ The Adventuring Party

                              ╔═══════════════════════════════════════════════════╗
                              ║   ★  P A R T Y   R O S T E R  ★                    ║
                              ║   These brave heroes joined the quest to slay      ║
                              ║   the dreaded budget-goblins of Pocket Pixel.      ║
                              ╚═══════════════════════════════════════════════════╝
party size quest
ali-ahnaf
ali-ahnaf

Guild Master
namahu
namahu

Ranger
Adolinnn
Adolinnn

Mage
Diyaaa-12
Diyaaa-12

Mage
uttam12331
uttam12331

Mage
Ayu360
Ayu360

Knight
Wasif123-rgb
Wasif123-rgb

Knight
Mas1101
Mas1101

Knight
sihab-hasan
sihab-hasan

Knight
Omee1612
Omee1612

Knight
nazifa-r
nazifa-r

Knight
jemifish0-0
jemifish0-0

Knight
isratarna
isratarna

Knight
developmentwithparth1311
developmentwithparth1311

Knight
afija0022-655
afija0022-655

Knight
Asif177164
Asif177164

Knight
Arina-Arni
Arina-Arni

Knight
AimanCrafts
AimanCrafts

Monk
SheikhMahmudArman
SheikhMahmudArman

Monk
Talha-Morshed
Talha-Morshed

Monk
tarannum007
tarannum007

Monk
tirtha-96
tirtha-96

Monk
siyamzawad190
siyamzawad190

Monk
abidhasan176
abidhasan176

Monk
afra012
afra012

Monk
ahona030
ahona030

Monk
ayesha523
ayesha523

Monk
eshaaaa1212
eshaaaa1212

Monk
faysaliqbal007
faysaliqbal007

Monk
irin123-hash
irin123-hash

Monk
isKaushik2
isKaushik2

Monk
mashru04
mashru04

Monk
minhalriaz
minhalriaz

Monk
nafi0001
nafi0001

Monk
proyas2005
proyas2005

Monk
razihasan98
razihasan98

Monk
0xAhnaf
0xAhnaf

Monk
ssirajussalikin119
ssirajussalikin119

Monk
tashrik404
tashrik404

Monk
raiyannewaz
raiyannewaz

Monk
MFA-G
MFA-G

Monk
Srabon006
Srabon006

Monk
HmsRafin
HmsRafin

Monk
Ishraq970
Ishraq970

Monk
Ishrat-alt
Ishrat-alt

Monk
IsratHossainSnigdha
IsratHossainSnigdha

Monk
Joyaaa-91
Joyaaa-91

Monk
joyanta07
joyanta07

Monk
Khubaira
Khubaira

Monk
miftahuljannat850-netizen
miftahuljannat850-netizen

Monk
mubasshirahin
mubasshirahin

Monk
munawarmoon
munawarmoon

Monk
nazifa-haque39
nazifa-haque39

Monk
tuwang2301
tuwang2301

Monk
urmee111
urmee111

Monk
NujhatMaliha99
NujhatMaliha99

Monk
RaisulSifat
RaisulSifat

Monk
CodeWizard973
CodeWizard973

Monk
Sadman-Wolfie
Sadman-Wolfie

Monk
SarahZaman1310
SarahZaman1310

Monk

🗡️ Want to join the party? Grab a quest from the issue board and roll for initiative.


License

MIT — do whatever you want with it. See LICENSE for details.


Made with ☕ and a lot of pixel art inspiration.

⬆ Back to top

About

A retro RPG themed expense tracker for managing income, expenses, savings, and recurring finances

Topics

Resources

Stars

14 stars

Watchers

0 watching

Forks

Releases

Contributors

Languages