A comprehensive full-stack expense tracker application built with React, TypeScript, Express.js, and SQLite. Track your finances, set budgets, visualize spending patterns, and export your data.
- Transaction Management: Add, edit, and delete income/expense transactions
- Category System: Predefined and custom categories for better organization
- Budget Tracking: Set budgets by category or overall spending
- Data Visualization: Interactive charts showing spending trends and patterns
- Monthly Comparisons: Compare income and expenses across multiple months
- Export Functionality: Export transaction data to CSV or JSON formats
- Theme Toggle: Light/Dark mode with automatic system preference detection
- Monorepo Architecture: Unified codebase with shared types between frontend and backend
- Type Safety: End-to-end TypeScript for better developer experience
- RESTful API: Well-structured API endpoints following REST principles
- Component Organization: Structured folders with separated concerns (logic vs. styling)
- Theme Support: Light/Dark mode with system preference detection and session persistence
- Responsive Design: Mobile-friendly interface using Styled Components
- State Management: React Context API for efficient state handling
- Data Validation: Zod schemas for consistent validation across the stack
- React 18 - UI library
- TypeScript - Type safety
- Vite - Build tool and dev server
- Styled Components - CSS-in-JS styling
- React Router - Client-side routing
- Recharts - Data visualization
- Axios - HTTP client
- Express.js - Web framework
- TypeScript - Type safety
- SQLite - Database
- better-sqlite3 - SQLite driver
- Zod - Schema validation
- csv-stringify - CSV export
PennyWise/
├── packages/
│ ├── client/ # React frontend
│ │ ├── src/
│ │ │ ├── components/
│ │ │ │ ├── common/ # Reusable UI components
│ │ │ │ │ ├── Button/
│ │ │ │ │ │ ├── index.tsx # Button component
│ │ │ │ │ │ └── styled.ts # Button styled components
│ │ │ │ │ ├── Card/
│ │ │ │ │ │ ├── index.tsx # Card component
│ │ │ │ │ │ └── styled.ts # Card styled components
│ │ │ │ │ ├── Input/
│ │ │ │ │ │ ├── index.tsx # Input components
│ │ │ │ │ │ └── styled.ts # Input styled components
│ │ │ │ │ ├── Modal/
│ │ │ │ │ │ ├── index.tsx # Modal component
│ │ │ │ │ │ └── styled.ts # Modal styled components
│ │ │ │ │ ├── ThemeToggle/
│ │ │ │ │ │ ├── index.tsx # Theme toggle component
│ │ │ │ │ │ └── styled.ts # Toggle styled components
│ │ │ │ │ └── index.tsx # Common components exports
│ │ │ │ ├── layout/ # Layout components
│ │ │ │ │ ├── Header/
│ │ │ │ │ │ ├── index.tsx # Header component
│ │ │ │ │ │ └── styled.ts # Header styled components
│ │ │ │ │ └── index.tsx # Layout exports
│ │ │ │ └── index.tsx # All components exports
│ │ │ ├── context/ # React Context providers
│ │ │ │ ├── AppContext.tsx
│ │ │ │ ├── BudgetContext.tsx
│ │ │ │ ├── ThemeContext.tsx
│ │ │ │ └── TransactionContext.tsx
│ │ │ ├── pages/ # Page components
│ │ │ │ ├── Dashboard/
│ │ │ │ │ ├── index.tsx # Dashboard page
│ │ │ │ │ └── styled.ts # Dashboard styled components
│ │ │ │ ├── Transactions/
│ │ │ │ │ ├── index.tsx # Transactions page
│ │ │ │ │ └── styled.ts # Transactions styled components
│ │ │ │ ├── Budgets/
│ │ │ │ │ ├── index.tsx # Budgets page
│ │ │ │ │ └── styled.ts # Budgets styled components
│ │ │ │ ├── Analytics/
│ │ │ │ │ ├── index.tsx # Analytics page
│ │ │ │ │ └── styled.ts # Analytics styled components
│ │ │ │ └── index.tsx # All pages exports
│ │ │ ├── services/ # API client services
│ │ │ │ ├── api.ts
│ │ │ │ ├── analyticsService.ts
│ │ │ │ ├── budgetService.ts
│ │ │ │ ├── categoryService.ts
│ │ │ │ ├── exportService.ts
│ │ │ │ └── transactionService.ts
│ │ │ ├── styles/ # Global styles & shared styled components
│ │ │ │ ├── GlobalStyles.ts
│ │ │ │ ├── styled.ts # Shared styled components (Container, Grid, etc.)
│ │ │ │ └── theme.ts # Light/Dark theme definitions
│ │ │ ├── App.tsx
│ │ │ └── main.tsx
│ │ └── package.json
│ │
│ ├── server/ # Express backend
│ │ ├── src/
│ │ │ ├── config/ # Configuration
│ │ │ ├── controllers/ # Route handlers
│ │ │ ├── services/ # Business logic
│ │ │ ├── repositories/ # Data access layer
│ │ │ ├── routes/ # API routes
│ │ │ ├── middleware/ # Express middleware
│ │ │ └── database/ # Migrations & seeds
│ │ └── package.json
│ │
│ └── shared/ # Shared types & validators
│ ├── src/
│ │ ├── types/ # TypeScript interfaces
│ │ ├── validators/ # Zod schemas
│ │ └── constants/ # Shared constants
│ └── package.json
│
└── package.json # Root package with workspaces
Each component follows a consistent structure:
ComponentName/
├── index.tsx # Component logic and exports
└── styled.ts # Styled-components for this component
This pattern provides:
- Clear separation between logic and styling
- Easy navigation with consistent naming
- Reusability through centralized exports
- Maintainability with isolated concerns
- Node.js >= 18.0.0
- npm >= 9.0.0
Important for WSL Users: This project requires native Linux Node.js. If you're using WSL with Windows npm, you'll encounter symlink errors. Follow the installation steps below to set up Node.js properly in WSL.
-
Install Node.js in WSL using nvm
If you don't have nvm installed:
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bashLoad nvm and install Node.js:
export NVM_DIR="$HOME/.nvm" [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" nvm install 20 nvm use 20
Add nvm to your
~/.bashrcfor future sessions:echo 'export NVM_DIR="$HOME/.nvm"' >> ~/.bashrc echo '[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"' >> ~/.bashrc source ~/.bashrc
-
Navigate to the project directory
cd PennyWise -
Install dependencies
Install dependencies for each package:
# Install shared package dependencies cd packages/shared && npm install && cd ../.. # Install server dependencies cd packages/server && npm install && cd ../.. # Install client dependencies cd packages/client && npm install && cd ../.. # Install root dependencies npm install
-
Build the shared package
cd packages/shared && npm run build && cd ../..
-
Set up environment variables
The
.envfile should already exist inpackages/server/with these values:PORT=3000 NODE_ENV=development CORS_ORIGIN=http://localhost:5173
Run both frontend and backend concurrently:
npm run devThis will start:
- Backend server on http://localhost:3000
- Frontend dev server on http://localhost:5173
- Database will be automatically initialized with default categories
For WSL users: Make sure you've loaded nvm in your current session:
export NVM_DIR="$HOME/.nvm" && [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"Backend only:
npm run serverFrontend only:
npm run clientOnce running, open your browser and navigate to:
- Frontend: http://localhost:5173
- Backend API: http://localhost:3000/api/v1
- Health Check: http://localhost:3000/health
Build all packages (shared package is built first automatically):
npm run buildBuild specific packages:
# Build shared package (must be built first)
cd packages/shared && npm run build
# Build server
npm run build:server
# Build client
npm run build:clientNote: Always build the shared package before building server or client packages.
http://localhost:3000/api/v1
GET /transactions- Get all transactions (with filters)POST /transactions- Create a transactionPUT /transactions/:id- Update a transactionDELETE /transactions/:id- Delete a transactionGET /transactions/summary- Get transaction summaryGET /transactions/by-category- Get spending by category
GET /categories- Get all categoriesPOST /categories- Create custom categoryPUT /categories/:id- Update custom categoryDELETE /categories/:id- Delete custom category
GET /budgets- Get all budgetsPOST /budgets- Create a budgetPUT /budgets/:id- Update a budgetDELETE /budgets/:id- Delete a budgetGET /budgets/current- Get active budgets with progress
GET /analytics/monthly-comparison- Monthly comparison dataGET /analytics/trends- Spending trends
GET /export/csv- Export transactions as CSVGET /export/json- Export transactions as JSON
The application uses SQLite with the following tables:
categories
- Default categories: Food & Dining, Transportation, Shopping, Entertainment, Bills & Utilities, Healthcare, Housing, Personal (expenses)
- Income categories: Salary, Freelance, Investments, Other Income
- Support for custom user-created categories
transactions
- Links to categories
- Stores amount, type (income/expense), description, and date
- Indexed for performance
budgets
- Can be category-specific or overall
- Supports weekly, monthly, and yearly periods
- Tracks progress automatically
On first run, the server will automatically:
- Create the database schema
- Seed default categories
The database file is created at: packages/server/src/database/pennywise.db
- Real-time summary of total income, expenses, and net balance
- Active budget progress bars with visual indicators
- Color-coded metrics (green for income, red for expenses)
- Full CRUD operations
- Filter by date range, category, and type
- Pagination support for large datasets
- Quick add with modal form
- Create budgets for specific categories or overall spending
- Real-time progress tracking
- Visual alerts when budgets are exceeded
- Support for different time periods (weekly, monthly, yearly)
- Monthly Comparison: Bar chart showing income vs expenses over 6 months
- Spending Trends: Line chart visualizing financial trends
- Category Breakdown: Pie chart showing expense distribution
- Export Options: Download data in CSV or JSON format
The project follows a clean architecture pattern:
Backend
- Controllers: Handle HTTP requests/responses
- Services: Business logic and orchestration
- Repositories: Data access and database operations
- Middleware: Request validation and error handling
Frontend
- Components: Organized by feature/purpose with separation of concerns
- Each component has its own folder with
index.tsx(logic) andstyled.ts(styles) - Common: Reusable UI components (Button, Card, Input, Modal, ThemeToggle)
- Layout: Structural components (Header)
- Each component has its own folder with
- Context: React Context providers for global state management
- Pages: Route-level components following the same folder structure
- Services: API client functions for backend communication
- Styles: Shared styled-components and theme configuration
Backend:
- Add types in
packages/shared/src/types/ - Add validation in
packages/shared/src/validators/ - Create repository in
packages/server/src/repositories/ - Create service in
packages/server/src/services/ - Create controller in
packages/server/src/controllers/ - Add routes in
packages/server/src/routes/
Frontend:
- Create component folder in
packages/client/src/components/common/ComponentName/ - Add component logic in
index.tsx - Add styled components in
styled.ts - Export component in the parent
index.tsx - Create frontend service (if needed) in
packages/client/src/services/ - Use component in pages or other components
Example: Adding a new Button variant
# Component already exists, just update styled.ts
packages/client/src/components/common/Button/styled.tsExample: Adding a new component
# Create folder and files
mkdir packages/client/src/components/common/NewComponent
touch packages/client/src/components/common/NewComponent/index.tsx
touch packages/client/src/components/common/NewComponent/styled.ts
# Export in parent index.tsx
echo "export { NewComponent } from './NewComponent';" >> packages/client/src/components/common/index.tsxIf you see errors like EISDIR: illegal operation on a directory, symlink:
- Cause: Using Windows npm in WSL instead of native Linux npm
- Solution: Follow the nvm installation steps in the Installation section above
If you see node: command not found:
export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"To make this permanent, add to ~/.bashrc:
echo 'export NVM_DIR="$HOME/.nvm"' >> ~/.bashrc
echo '[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"' >> ~/.bashrcIf the server can't find the shared module:
cd packages/shared
npm run build
cd ../..If port 3000 or 5173 is in use:
- Backend: Change
PORTinpackages/server/.env - Frontend: Change port in
packages/client/vite.config.ts
Delete the database file and restart the server:
rm packages/server/src/database/pennywise.db
npm run serverIf you see TypeScript errors about unused variables:
cd packages/server
npm installTo perform a complete clean reinstall:
# Remove all node_modules and build artifacts
rm -rf node_modules packages/*/node_modules packages/*/dist
# Reinstall dependencies
cd packages/shared && npm install && npm run build && cd ../..
cd packages/server && npm install && cd ../..
cd packages/client && npm install && cd ../..
npm install- User authentication and multi-user support
- Recurring transactions
- Receipt upload and OCR
- Multi-currency support
- Bank account integration (Plaid API)
- Mobile app (React Native)
- Budget recommendations using ML
- Bill payment reminders
- Spending insights and trends
- Data backup and sync
MIT License - feel free to use this project for learning or portfolio purposes.
This is a portfolio project, but suggestions and feedback are welcome! Feel free to open an issue or submit a pull request.
Built with ❤️ using React, TypeScript, and Express.js