An open API service indexing awesome lists of open source software.

https://github.com/nothing9537/financer

Financer is a production-minded finance dashboard (Next.js 15, TS) with Plaid (sandbox) bank linking, secure server exchange, accounts/transactions sync (PFC mapping), CSV import/export with TF-IDF auto-categorization, analytics charts, URL-synced filters, and a subscription paywall via Lemon Squeezy (Checkout/Portal + webhook)
https://github.com/nothing9537/financer

csv-import drizzle-orm finance fintech honojs lemonsqueezy nextjs plaid react react-query recharts rpc sentry shadcn-ui tailwind-css type-safety typescript zustand

Last synced: 2 months ago
JSON representation

Financer is a production-minded finance dashboard (Next.js 15, TS) with Plaid (sandbox) bank linking, secure server exchange, accounts/transactions sync (PFC mapping), CSV import/export with TF-IDF auto-categorization, analytics charts, URL-synced filters, and a subscription paywall via Lemon Squeezy (Checkout/Portal + webhook)

Awesome Lists containing this project

README

          

# Financer — Personal Finance Dashboard (Next.js 15, Plaid, Lemon Squeezy)

**Stack highlights:** Next.js (App Router, RSC), TypeScript, Clerk (auth), Hono (typed API), Drizzle ORM + Neon, TanStack Query/Table, Recharts, Tailwind v4 + shadcn/ui, Sentry.
**Fintech:** Plaid **Sandbox** integration (link, exchange, accounts, transactions), subscriptions via **Lemon Squeezy** (Checkout + Customer Portal + webhook, DB status).

> ⚠️ **Educational/demo project.** Plaid runs in **Sandbox** only. Do **not** connect real bank accounts.

---

## TL;DR

- Link a bank via **Plaid (sandbox)** → import **accounts** & **transactions**
- Explore data with **filters/search/sorting/pagination**, inline edits, CSV **export**
- **CSV import** with **TF-IDF auto-categorization** + merchant rules
- Premium flows are **paywalled** via **Lemon Squeezy** subscriptions
- Production touches: **Clerk auth**, **typed Hono API**, **Drizzle migrations**, **Sentry**, typed payload validation (Zod)

---

## ✨ Key Features

### Fintech & Data
- **Plaid Sandbox**: Link flow, server-side public_token exchange, persisted **accounts** and **transactions** (with Plaid Personal Finance Category mapping).
- **CSV Import**: Client-side parsing (PapaParse) with **TF-IDF**-based auto-categorization and merchant rules; **CSV Export** for the active selection.
- **Overview Analytics**: Daily income/expense time series (gap-filled), category share (pie), and deltas vs previous period.

### Subscriptions & Access
- **Lemon Squeezy**:
- `POST /api/subscriptions/checkout` → URL to **Checkout** or **Customer Portal** (if already subscribed)
- `GET /api/subscriptions/current` → current status
- `POST /api/subscriptions/webhook` → HMAC-verified upsert in DB
- `Test credentials` → Card Number: 4242 4242 4242 4242, Expiration date: Any in the future, CVV: any, Rest - any valid credentials to bypass LemonSqueezy validation.
- **Paywall gating**: Plaid linking and CSV import require an active subscription.

### App UX
- **Transactions Table** (TanStack Table): sorting, selection, inline edits (sheet modals).
- **URL-synced filters**: account & date range filters survive refresh and are shareable (deep-links).
- **Accounts & Categories**: CRUD with modals; first-run import of the Plaid PFC taxonomy.

### Observability & Safety
- **Sentry** (server/edge) with prod-only enablement and `/monitoring` tunnel.
- **Hono + Zod** validation on API routes; **Clerk**-backed auth guard.
- **Drizzle ORM + Neon** with migrations committed.

---

## 🧭 Project Status

- ✅ Next.js 15 (App Router, RSC) + TypeScript + Tailwind v4/shadcn
- ✅ Auth (Clerk) + middleware guard
- ✅ Hono API (`/api`) + Zod validation
- ✅ Drizzle ORM + Neon driver; migrations via `drizzle-kit`
- ✅ **Plaid Sandbox**: link → exchange → accounts + transactions → category mapping
- ✅ **Lemon Squeezy**: checkout, customer portal, webhook; DB holds subscription status
- ✅ CSV import (TF-IDF categorization) and CSV export
- ✅ Overview charts & summary
- ✅ Sentry wiring (prod-only), TanStack Query provider
- ⏳ CI (GitHub Actions)

---

## 🏗️ Architecture

**Frontend-first, App Router**

- **RSC** pages for first render and streaming; interactive “islands” as client components.
- **Feature-Sliced layers**: `shared → entities → features → widgets → app pages`.

**API / Server**

- `src/app/api/[[...route]]/route.ts` — **Hono** router mounted at `/api`; subroutes:
- `/plaid`, `/summary`, `/accounts`, `/categories`, `/transactions`, `/subscriptions`
- Request validation via `@hono/zod-validator`; auth via `@hono/clerk-auth`.
- **DB (Drizzle ORM / Postgres, Neon)** — core tables:
- `accounts` (id, name, userId, plaid_id)
- `categories` (id, name, userId, plaid_id, unique(userId, plaid_id))
- `transactions` (id, amount **milliunits**, payee, notes, date, accountId, categoryId)
- `connected_banks` (id, userId, accessToken, cursor, itemId)
- `subscriptions` (id, userId **unique**, subscriptionId **unique**, status)

**Data & State**

- TanStack Query hooks under `src/entities/**/api` for server data.
- Small local/Zustand stores where needed (e.g., CSV flow).
- Utilities for money math (milliunits), Plaid taxonomy, formatting, and URL filters.

**UI**

- shadcn/ui components, Tailwind v4, lucide icons.

---

## 🔌 API Surface (selected)

`/api/plaid`
- **POST** `/create-link-token` → `{ token }`
- **POST** `/exchange-public-token` `{ public_token }` → persist bank + accounts + transactions
- **GET** `/connected-bank` → current connection
- **DELETE** `/connected-bank` → remove connection
- **POST** `/webhook` (Plaid) → sync by cursor

`/api/subscriptions`
- **GET** `/current` → subscription status
- **POST** `/checkout` → `{ url }` (Checkout or Customer Portal)
- **POST** `/webhook` (Lemon Squeezy) → HMAC verify + DB upsert

`/api/summary`
- **GET** `/` with `?from=YYYY-MM-DD&to=YYYY-MM-DD&accountId=…` → time series + category share + deltas

`/api/accounts`, `/api/categories`, `/api/transactions` — CRUD, bulk ops, search & pagination.

---

## 🧰 Tech Stack

**Frontend:** Next.js 15 · React 19 · TypeScript · Tailwind v4 · shadcn/ui · TanStack Query/Table · Recharts
**API:** Hono · Zod · `@hono/clerk-auth` · Route Handlers (Node runtime)
**Auth:** Clerk
**DB:** Drizzle ORM + `@neondatabase/serverless`
**Fintech:** Plaid SDK (Sandbox) · **Lemon Squeezy** SDK (checkout/portal/webhooks)
**Observability:** Sentry (server/edge)
**Data tooling:** PapaParse, uuid, date-fns, lodash, zustand

---

## 🔐 Security & Privacy

- **Sandbox only** for Plaid. Never use real banking credentials.
- Store tokens server-side; in production, **encrypt at rest** and scrub from logs.
- HttpOnly cookies via Clerk; Lemon webhooks verified with HMAC.
- Middleware protects the app (public routes are limited to sign-in/up and the subscription webhook).

---

## Quick Start Guide

1) **Create accounts & collect tokens (fill `.env`)**
- **Clerk**: `NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY`, `CLERK_SECRET_KEY`, `CLERK_PUBLISHABLE_KEY - same as NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY`
- **Database (Neon Postgres)**: `DEVELOPMENT_DB_URL`, `PRODUCTION_DB_URL`
- **Plaid (Sandbox)**: `PLAID_CLIENT_TOKEN`, `PLAID_SECRET_TOKEN` *(use sandbox keys only)*
- **Lemon Squeezy**: `LEMONSQUEEZY_STORE_ID`, `LEMONSQUEEZY_PRODUCT_ID`, `LEMONSQUEEZY_API_KEY`, `LEMONSQUEEZY_WEBHOOK_SECRET`
- **App URL**: `NEXT_PUBLIC_APP_URL` (e.g., `http://localhost:3000` and later your vercel app URL)
- **Sentry (optional, prod/preview only)**: `NEXT_PUBLIC_SENTRY_DSN`, `SENTRY_AUTH_TOKEN`

2) **Set up the project locally**
```bash
git clone && cd
cp .env.example .env # paste all tokens from step 1
pnpm i # or: bun i
pnpm db:generate && pnpm db:migrate
pnpm dev # app on http://localhost:3000
```

3) **Connect Lemon Squeezy webhooks (recommended via ngrok)**
- Install **ngrok** locally, then expose your dev server:
```bash
ngrok http 3000
```
- Copy the **HTTPS Forwarding URL** from ngrok and set it as your Lemon Squeezy webhook endpoint:
```
https://.ngrok.io/api/subscriptions/webhook
```
- Save the webhook in Lemon Squeezy and use your `LEMONSQUEEZY_WEBHOOK_SECRET` in `.env`.

4) **Sign in & try the flows**
- Go to `/sign-in` (Clerk) → log in.
- Test **subscription** (Checkout/Portal) and **Plaid (sandbox)** linking.
- Use **filters/analytics** and **CSV import/export**.

### Environment variables (`.env.example`)

- **Clerk:** `NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY`, `CLERK_SECRET_KEY`, `CLERK_PUBLISHABLE_KEY - same as NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY`
- **DB:** `PRODUCTION_DB_URL`, `DEVELOPMENT_DB_URL` (Neon)
- **Sentry:** `NEXT_PUBLIC_SENTRY_DSN` (prod/preview), `SENTRY_AUTH_TOKEN` (CI)
- **Plaid (sandbox):** `PLAID_CLIENT_TOKEN`, `PLAID_SECRET_TOKEN`
- **App URL:** `NEXT_PUBLIC_APP_URL`
- **Lemon Squeezy:** `LEMONSQUEEZY_STORE_ID`, `LEMONSQUEEZY_PRODUCT_ID`, `LEMONSQUEEZY_API_KEY`, `LEMONSQUEEZY_WEBHOOK_SECRET`

> **Sentry in development**: enabled only in production/preview when a DSN is present. Locally, keep `NEXT_PUBLIC_SENTRY_DSN` empty.

---

## 📊 Metrics & Charts

- `/summary` aggregates daily series, computes **income/expenses/remaining** and deltas vs the previous window.
- `fillMissingDays` for continuous time series; category share pie for composition.
- Filters are URL-synced for deep links.

---

## ✅ Code Quality & Best Practices — Review

**What’s strong**
- **Typed, modular design:** App Router with a clean split into entities/features/widgets; typed API via Hono + Zod; Drizzle schemas checked into repo.
- **Clear domain boundaries:** finance entities (accounts, categories, transactions) and subscription state kept separate and explicit.
- **Security posture:** Clerk for auth, HMAC verification on Lemon webhooks, Plaid confined to sandbox; tokens kept server-side.
- **Developer ergonomics:** Tailwind v4 + shadcn/ui, TanStack Query/Table; URL-synced filters improve UX and shareability.
- **Observability hooks:** Sentry wiring (server/edge), production-only enablement, monitoring endpoint.

**What to improve next (high-impact, short effort)**
1. **Encrypt tokens at rest** (Plaid access tokens) and add a scrubber middleware for logs.
2. **Rate limiting & idempotency**: apply per-IP/user limits on `/api/plaid/*`; idempotent handling for webhooks (keyed by `event.id`).
3. **Performance budget proof**: add a `/metrics` page (Web Vitals ingest) and include a Lighthouse (mobile) screenshot in README.
4. **Accessibility basics**: keyboard focus, ARIA labels for table controls, color-contrast ≥ 4.5:1, live regions for async updates.
5. **Error boundaries & friendly fallbacks**: empty/error/skeleton states are in place—add a global error boundary for unexpected crashes.
6. **Backups & migrations policy**: document Neon backup cadence and a simple rollback procedure (Drizzle migrations).
7. **Internationalization / currency**: centralize currency/locale formatting and (optionally) add an i18n stub for future expansion.

---

## 🗺️ Roadmap

- Budgets & alerts (per category), monthly/periodic reports (CSV/PDF)
- i18n & multi-currency
- Scheduled sync (cron)
- At-rest encryption for tokens/secrets
- CI (GitHub Actions) pipeline (lint → typecheck → build → deploy)

---

## 📂 Notable Source Layout

- `src/app/(dashboard)/*` — Overview, Transactions, Accounts, Categories, Settings
- `src/app/api/[[...route]]/*` — Hono subroutes (plaid, subscriptions, summary, …)
- `db/schemas/*` — Drizzle tables & relations
- `src/entities/*` — typed API hooks, types, small stores (per domain)
- `src/features/*` — user flows (plaid-connect, csv-import, subscribe-button, filters, …)
- `src/widgets/*` — composite UI blocks (tables, sheets, header, summary)
- `src/shared/*` — UI kit, utils, hooks, constants

---

## 📄 License

MIT

---

**Notes**
This README reflects the current codebase: Next.js 15 + Clerk + Hono API + Drizzle/Neon; Plaid (Sandbox) and Lemon Squeezy are integrated at the API level with paywall gating; CSV import + TF-IDF categorization; Sentry wiring in prod/preview. If you change env keys or route names, update the sections above accordingly.