Monity is a secure personal finance tracker built with Next.js and InsForge.
It includes:
- Email/password authentication with optional Google OAuth
- Profile persistence in
user_profiles - Account management (bank, cash, cards)
- Transaction tracking (income and expense)
- Category management (system + custom)
- Monthly budgets with utilization tracking
- Dashboard aggregates (balances, month totals, spending by category, recent transactions)
- Next.js 16 (App Router)
- React 19
- TypeScript (strict mode)
- Tailwind CSS 4
- InsForge SDK (
@insforge/sdk)
app/
api/
auth/ # auth and session endpoints
accounts/ # account CRUD
categories/ # category CRUD
transactions/ # transaction CRUD
budgets/ # budget CRUD
dashboard/ # analytics endpoint
user/me/ # profile read/update
sign-in|sign-up|verify-email/
accounts|transactions|categories|budgets|dashboard/
components/
auth/ # auth layouts/forms
finance/ # finance shell and managers
ui/ # shared UI utilities (toasts)
insforge/migrations/
001_auth.sql # user profile table + RLS + trigger
002_finance.sql # finance tables + indexes + RLS + seed categories
lib/
insforge/ # client/session/cookie/api helpers
finance/validation.ts # API payload parsing/validation
Brand assets live in public/ and should be used consistently across app surfaces.
Available files:
public/monity-logo.pngandpublic/monity-logo.webp: primary logo (icon + wordmark) for light backgrounds.public/monity-logo_black.pngandpublic/monity-logo_black.webp: navy wordmark variant for neutral/light UI where stronger text contrast is needed.public/file.svg: primary SVG logo for scalable UI placements when vector output is preferred.public/monity-icon.pngandpublic/monity-icon.webp: icon-only mark for compact placements (favicon-like, app tiles, sidebar, avatar-style badges).public/image-guideline.png: reference board showing approved color and composition variants.
Usage rules:
- Prefer
.webpin UI for better payload size; keep.pngas fallback for environments that need it. - Prefer
.svgfor sharp rendering at varied sizes when no raster effects are required. - Do not stretch logos non-uniformly; preserve original aspect ratio.
- Keep clear space around logos (at least the icon's smallest bar width on each side).
- Use icon-only assets only when horizontal space is constrained.
- On dark surfaces, use gradient/bright variants with sufficient contrast.
- Avoid adding drop-shadows, recoloring, or overlay effects that alter brand colors.
Implementation notes (Next.js):
- Use
next/imagefor raster assets (.webp,.png) to get automatic optimization. - Place static brand references in
public/and load them via absolute paths (for example,/monity-logo.webp).
Create .env.local:
NEXT_PUBLIC_INSFORGE_URL=https://your-project.insforge.app
NEXT_PUBLIC_INSFORGE_ANON_KEY=your-anon-key
NEXT_PUBLIC_APP_URL=http://localhost:3000Supported aliases in code:
- InsForge URL:
NEXT_PUBLIC_INSFORGE_URLorINSFORGE_URLorINSFORGE_BASE_URL - Anon key:
NEXT_PUBLIC_INSFORGE_ANON_KEYorINSFORGE_ANON_KEY - App URL:
NEXT_PUBLIC_APP_URLorAPP_URL
- Install dependencies.
- Configure
.env.local. - Apply SQL migrations in
insforge/migrations/to your InsForge Postgres database. - Start the app.
pnpm install
pnpm devProduction commands:
pnpm build
pnpm startLint:
pnpm lintMonity uses InsForge auth and stores session tokens in HTTP-only cookies:
monity_access_token(1 day)monity_refresh_token(30 days)monity_oauth_code_verifier(10 minutes, OAuth handshake)
Supported auth flows:
- Register with email/password
- Login with email/password
- Verify email with OTP
- Resend verification email
- Refresh session using refresh token
- Logout
- Google OAuth sign-in (
/api/auth/oauth/google/startand callback)
The app upserts profile data in public.user_profiles on successful auth.
- Creates
public.user_profiles - Enables RLS
- Adds policies so authenticated users can only read/write their own profile
- Adds shared
public.set_updated_at()trigger function
Creates:
public.accountspublic.categoriespublic.transactionspublic.budgets
Also includes:
- Validation constraints (enum-like checks, positive amounts, period month first-day check)
- Indexes for user/date/category/account lookups
- Seeded system categories (Food, Transport, Housing, etc.)
- RLS policies to scope data by authenticated user
- Triggers for
updated_at
All finance/profile endpoints require an authenticated session cookie.
POST /api/auth/register- body:
{ email, password, name? }
- body:
POST /api/auth/login- body:
{ email, password }
- body:
POST /api/auth/logoutPOST /api/auth/refresh- body:
{ refreshToken? }(falls back to refresh cookie)
- body:
POST /api/auth/verify-email- body:
{ email, otp }
- body:
POST /api/auth/resend-verification- body:
{ email }
- body:
GET /api/auth/meGET /api/auth/oauth/google/startGET /api/auth/oauth/google/callback
GET /api/user/mePATCH /api/user/me- body:
{ displayName }(1-80 chars after normalization)
- body:
GET /api/accountsPOST /api/accountsPATCH /api/accounts/:idDELETE /api/accounts/:id
Account payload:
{
"name": "Main Checking",
"type": "bank",
"initialBalance": 1200,
"currency": "USD",
"isActive": true
}Allowed account type values:
bankcashcredit_carddebit_card
GET /api/categoriesPOST /api/categoriesPATCH /api/categories/:idDELETE /api/categories/:id
Category payload:
{
"name": "Groceries",
"type": "expense",
"color": "#22C55E",
"icon": "cart"
}Allowed category type values:
incomeexpense
System categories are read-only; only user-owned non-system categories can be updated/deleted.
GET /api/transactions?accountId=&categoryId=&limit=POST /api/transactionsPATCH /api/transactions/:idDELETE /api/transactions/:id
Transaction payload:
{
"accountId": "uuid",
"categoryId": "uuid",
"type": "expense",
"amount": 34.9,
"description": "Lunch",
"transactionDate": "2026-04-12"
}Validation highlights:
amount > 0transactionDateformatYYYY-MM-DD- account must belong to current user
- category must be either system category or current user's category
GET /api/budgets?periodMonth=YYYY-MM-01POST /api/budgetsPATCH /api/budgets/:idDELETE /api/budgets/:id
Budget payload:
{
"categoryId": "uuid",
"periodMonth": "2026-04-01",
"limitAmount": 500
}Validation highlights:
periodMonthmust be first day of month (YYYY-MM-01)limitAmount > 0- upsert conflict key is
(user_id, category_id, period_month)
GET /api/dashboard
Returns aggregated finance data:
- totals (
total_balance,month_income,month_expense,month_net) - account balances (
initial_balance, computedcurrent_balance) - recent transactions (latest 10)
- spending by category (current month)
- budgets with utilization and exceeded flag
Public/auth:
/(landing, redirects to/dashboardwhen authenticated)/sign-in/sign-up/verify-email
Protected finance pages:
/dashboard/accounts/transactions/categories/budgets
Core request parsing lives in lib/finance/validation.ts:
- Currency must be 3-letter uppercase code
- Amounts are numeric and rounded to 2 decimals
- Date parsing enforces
YYYY-MM-DD - Transaction and category types are strictly validated
next.config.tscurrently uses default configuration.- TypeScript path alias
@/*maps to project root. strictmode is enabled intsconfig.json.
Private project.