To develop a Personal Finance Tracker with Next.js, NestJS, PostgreSQL, and Prisma, here’s a breakdown of the features, functionality, and components it could include:
Features and Functionality
1. User Authentication and Profiles
- Sign-up/Login: Implement user registration and authentication (using JWT or OAuth).
- Profile Management: Users can update their profiles, including financial goals and settings.
- Role-based Access: Admin users for managing categories, user data, etc.
2. Transaction Management
- Add Transactions: Users can manually add their income and expense transactions.
- Transaction Categorization: Transactions automatically or manually categorized (e.g., groceries, entertainment, bills).
- Recurring Transactions: Set up recurring transactions like rent or monthly salary.
- Bulk Import/Export: Ability to import transactions from bank statements (CSV or other formats) and export transaction history for personal records.
3. Expense and Income Tracking
- Income Sources: Track multiple income streams.
- Expense Categorization: Track all expenses by category, date, or merchant.
- Expense Trends: Visual representation of spending over time.
- Expense Tags: Users can tag expenses (e.g., “vacation,” “gift”) for better organization.
4. Budgeting Tools
- Create Budgets: Set up monthly/annual budgets by category (e.g., limit $500 for groceries).
- Track Budget Progress: Users receive real-time feedback on how much budget is left.
- Budget Alerts: Notifications when spending exceeds a budgeted category or when approaching limits.
5. Financial Reports and Analytics
- Income/Expense Reports: Generate monthly, quarterly, or yearly reports.
- Custom Reports: Ability to create custom reports by time range, category, or tags.
- Spending Insights: Provide insights on spending habits (e.g., largest expenses, unusual spending patterns).
- Graphs and Charts: Include visual tools (e.g., pie charts, bar graphs) to help users understand their financial status.
6. Goal Setting
- Create Financial Goals: Users can set financial goals (e.g., save $5000 for a trip).
- Track Goal Progress: Users can allocate savings or funds towards specific goals.
- Automated Savings Suggestions: Recommend savings amounts based on income/expenses.
7. Notifications and Alerts
- Budget Alerts: Notifications for exceeding spending limits or upcoming bills.
- Goal Progress Alerts: Reminders or notifications when users are on track or falling behind on goals.
- Recurring Bill Reminders: Alerts for recurring transactions (e.g., rent due).
- Custom Alerts: Users can set personalized alerts based on financial events or thresholds.
8. Financial Insights Dashboard
- Overview Page: Summarize key financial data (e.g., net worth, recent transactions, budget status).
- Top Expense Categories: List of categories where most money is being spent.
- Income vs. Expenses: Visual comparison of income versus expenses for a given time period.
9. Mobile App Features
- Real-time Sync: All data synchronized in real-time between web and mobile apps.
- Quick Transaction Entry: Fast access to log expenses or income from the mobile app.
- Budget Alerts on Mobile: Push notifications for exceeding budgets or reaching financial goals.
- Offline Mode: Users can log transactions offline, and they sync when back online.
10. Data Security and Privacy
- Encrypted Data Storage: All sensitive financial data is encrypted.
- Two-factor Authentication (2FA): Additional layer of security during login.
- User Consent for Data: Users can manage their privacy settings and control what data is stored.
Project Architecture
Frontend: Next.js
- State Management: Use libraries like Redux or Zustand to manage state (e.g., user data, transaction lists).
- UI Components: Build reusable components for budget tracking, financial reports, transaction tables, etc.
- Authentication: Integrate JWT or OAuth-based authentication for secure login and access.
- Responsive Design: Ensure the app works seamlessly on desktop and mobile.
- API Integration: Next.js will communicate with the NestJS backend through API calls (REST/GraphQL).
Backend: NestJS
- API Design: Expose REST or GraphQL endpoints for all necessary features (e.g., transaction CRUD, budget tracking, reports).
- Authentication and Authorization: Implement JWT-based user authentication and role-based access control (RBAC).
- Background Jobs: Schedule notifications, budget alerts, and periodic reports using NestJS background workers.
- Data Validation: Use DTOs (Data Transfer Objects) and validation pipelines to ensure proper input data.
Database: PostgreSQL
- Prisma ORM: Manage the database with Prisma for efficient queries and modeling.
- Database Schema: Design schemas for users, transactions, budgets, goals, categories, and reports.
- Database Relations: Handle relationships (e.g., users to transactions, budgets to categories) efficiently using Prisma.
- Data Aggregation: Use PostgreSQL functions for creating reports and analytics.
Mobile App: React Native (optional extension)
- Cross-platform Development: The mobile app will reuse the Next.js API endpoints and state management logic.
- Push Notifications: Implement mobile alerts for budgeting, goal tracking, or expense thresholds.
- Offline Sync: Add an offline mode that syncs when the user reconnects.
Potential Enhancements
- Bank Integration: Integrate with APIs like Plaid to automatically pull in bank transactions.
- Multi-Currency Support: Allow users to track expenses in different currencies.
- Investment Tracking: Provide tools to track stocks, bonds, or other investments.
- Tax Estimation: Include tax calculation and estimation tools for better financial planning.
This plan outlines a comprehensive personal finance tracker with robust features across the web and mobile platforms. Let me know if you want to further explore any specific area!
Here’s a suggested folder and file structure for the frontend of your Personal Finance Tracker project using Next.js. This structure is designed to handle all the features and functionality outlined earlier. It follows Next.js conventions while organizing the project for scalability and maintainability.
Project Folder Structure:
personal-finance-tracker-frontend/
├── public/
│ ├── assets/ # Static assets like images, icons, etc.
│ │ └── logo.png
│ └── favicon.ico
├── src/
│ ├── components/ # Reusable UI components
│ │ ├── auth/
│ │ │ ├── LoginForm.tsx
│ │ │ └── RegisterForm.tsx
│ │ ├── dashboard/
│ │ │ ├── BudgetOverview.tsx
│ │ │ ├── ExpenseChart.tsx
│ │ │ ├── IncomeChart.tsx
│ │ │ └── TransactionTable.tsx
│ │ ├── forms/
│ │ │ ├── AddTransactionForm.tsx
│ │ │ └── BudgetForm.tsx
│ │ ├── layouts/
│ │ │ ├── MainLayout.tsx
│ │ │ └── AuthLayout.tsx
│ │ ├── shared/
│ │ │ ├── Button.tsx
│ │ │ ├── Modal.tsx
│ │ │ └── InputField.tsx
│ │ └── notifications/
│ │ └── BudgetAlert.tsx
│ ├── contexts/ # React context for state management
│ │ ├── AuthContext.tsx
│ │ └── BudgetContext.tsx
│ ├── hooks/ # Custom hooks for reusable logic
│ │ ├── useAuth.tsx
│ │ ├── useTransactions.tsx
│ │ └── useBudgets.tsx
│ ├── pages/ # Next.js pages and routes
│ │ ├── api/ # Next.js API routes (if any are used)
│ │ ├── auth/ # Authentication-related pages
│ │ │ ├── login.tsx
│ │ │ └── register.tsx
│ │ ├── dashboard/ # Dashboard pages
│ │ │ ├── index.tsx
│ │ │ └── reports.tsx
│ │ ├── goals/ # Financial goals pages
│ │ │ ├── create.tsx
│ │ │ └── index.tsx
│ │ ├── transactions/ # Transactions-related pages
│ │ │ ├── add.tsx
│ │ │ └── index.tsx
│ │ ├── budgets/ # Budgeting pages
│ │ │ ├── create.tsx
│ │ │ └── index.tsx
│ │ ├── index.tsx # Landing or home page
│ │ └── _app.tsx # Custom App component (for global settings)
│ ├── services/ # Services for API requests and other logic
│ │ ├── authService.ts # Auth-related API calls
│ │ ├── transactionService.ts # API calls for transactions
│ │ ├── budgetService.ts # API calls for budgets
│ │ └── reportService.ts # API calls for reports
│ ├── store/ # State management (if using Redux or Zustand)
│ │ ├── index.ts # Configure store
│ │ ├── authSlice.ts # Auth state slice
│ │ └── budgetSlice.ts # Budget state slice
│ ├── styles/ # Global styles (CSS or Tailwind)
│ │ ├── globals.css # Global styles (or Tailwind imports)
│ │ └── tailwind.config.js # Tailwind configuration (if using)
│ ├── utils/ # Utility functions/helpers
│ │ ├── formatDate.ts # Helper to format dates
│ │ ├── validateForm.ts # Form validation logic
│ │ └── constants.ts # App-wide constants (e.g., budget categories)
│ └── types/ # TypeScript types/interfaces
│ ├── transaction.ts # Types for transactions
│ ├── budget.ts # Types for budgets
│ └── user.ts # Types for user/auth
├── .env # Environment variables (e.g., API keys)
├── next.config.js # Next.js configuration
├── tailwind.config.js # Tailwind CSS configuration (if using)
├── tsconfig.json # TypeScript configuration
├── package.json # Dependencies and scripts
└── README.md # Project documentation
Breakdown of Key Directories and Files
1. components/
- This is where all the reusable components live, organized by functionality (e.g., forms, shared, dashboard).
-
Shared Components:
Button
,Modal
,InputField
are reusable UI elements. - Auth Components: Login and registration forms.
-
Dashboard Components: Contains components to display user-specific financial data (e.g.,
ExpenseChart
,TransactionTable
). -
Notifications: Components like
BudgetAlert
to notify users when they're close to exceeding their budget.
2. contexts/
- Handles global state using React Context API for things like user authentication (
AuthContext.tsx
) and budget management (BudgetContext.tsx
).
3. hooks/
- Custom hooks to abstract and reuse logic across components, such as
useTransactions
,useAuth
, anduseBudgets
.
4. pages/
- The
pages/
directory is used by Next.js to generate routes. For example:-
/auth/login
maps tologin.tsx
for the login page. -
/transactions/add
maps toadd.tsx
for adding a new transaction.
-
-
_app.tsx
is a custom App component where you can wrap global settings like context providers or state management tools (e.g., Redux, Zustand).
5. services/
- Contains API service modules like
authService.ts
,transactionService.ts
, which handle the actual API calls to your backend (NestJS). - This keeps your API logic separate and clean, making it easier to maintain and update.
6. store/
- If you're using Redux or Zustand for global state management, this folder contains slices of state, such as
authSlice.ts
for handling user authentication andbudgetSlice.ts
for budget state. - The
index.ts
file sets up the store and combines reducers.
7. styles/
- Contains global styles like
globals.css
. If you're using Tailwind CSS, thetailwind.config.js
would go here to customize Tailwind’s default settings.
8. utils/
- Contains utility functions like
formatDate.ts
for date formatting andvalidateForm.ts
for form validation logic.constants.ts
is where you’d keep shared constants, like categories for transaction types.
9. types/
- Store your TypeScript interfaces and types here. Examples include
transaction.ts
,budget.ts
, anduser.ts
for defining the data structures used throughout the app.
Additional Notes:
- Responsive Design: Ensure all components are responsive to work on different screen sizes, especially if you plan to make this a mobile-first experience.
-
State Management: Choose between Redux, Zustand, or Next.js's built-in
useState
anduseContext
depending on the complexity of your app. - API Integration: The services layer will call your NestJS backend using Axios or Fetch.
-
Environment Variables: Use
.env
for storing environment variables like API keys.
This folder structure ensures that the project is well-organized and scalable, making it easier to manage as the application grows.
For the backend of your Personal Finance Tracker project, you will be using NestJS, PostgreSQL, and Prisma. Here's a comprehensive folder and file structure that supports all the features and functionality, ensuring scalability and maintainability.
Backend Folder Structure
personal-finance-tracker-backend/
├── prisma/
│ ├── migrations/ # Prisma migration files
│ ├── schema.prisma # Prisma schema for database models
│ └── seed.ts # Seed data script for initial data
├── src/
│ ├── auth/ # Authentication module
│ │ ├── auth.controller.ts # Auth API controllers
│ │ ├── auth.module.ts # Auth module definition
│ │ ├── auth.service.ts # Auth service (business logic)
│ │ ├── dto/
│ │ │ └── login.dto.ts # DTO for login
│ │ └── jwt.strategy.ts # JWT strategy for authorization
│ ├── users/ # Users module
│ │ ├── users.controller.ts # Users API controllers
│ │ ├── users.module.ts # Users module definition
│ │ ├── users.service.ts # Users service (business logic)
│ │ └── dto/
│ │ └── create-user.dto.ts # DTO for creating users
│ ├── transactions/ # Transactions module
│ │ ├── transactions.controller.ts # Transactions API controllers
│ │ ├── transactions.module.ts # Transactions module definition
│ │ ├── transactions.service.ts # Transactions service (business logic)
│ │ ├── dto/
│ │ │ ├── create-transaction.dto.ts # DTO for creating a transaction
│ │ │ └── update-transaction.dto.ts # DTO for updating a transaction
│ │ └── entities/
│ │ └── transaction.entity.ts # Transaction entity definition
│ ├── budgets/ # Budgets module
│ │ ├── budgets.controller.ts # Budgets API controllers
│ │ ├── budgets.module.ts # Budgets module definition
│ │ ├── budgets.service.ts # Budgets service (business logic)
│ │ ├── dto/
│ │ │ ├── create-budget.dto.ts # DTO for creating a budget
│ │ │ └── update-budget.dto.ts # DTO for updating a budget
│ │ └── entities/
│ │ └── budget.entity.ts # Budget entity definition
│ ├── reports/ # Reports module
│ │ ├── reports.controller.ts # Reports API controllers
│ │ ├── reports.module.ts # Reports module definition
│ │ ├── reports.service.ts # Reports service (business logic)
│ ├── goals/ # Financial goals module
│ │ ├── goals.controller.ts # Goals API controllers
│ │ ├── goals.module.ts # Goals module definition
│ │ ├── goals.service.ts # Goals service (business logic)
│ │ ├── dto/
│ │ │ ├── create-goal.dto.ts # DTO for creating a financial goal
│ │ │ └── update-goal.dto.ts # DTO for updating a goal
│ │ └── entities/
│ │ └── goal.entity.ts # Goal entity definition
│ ├── notifications/ # Notifications module
│ │ ├── notifications.controller.ts # Notifications API controllers
│ │ ├── notifications.module.ts # Notifications module definition
│ │ ├── notifications.service.ts # Notification services (business logic)
│ ├── config/ # App configuration files
│ │ ├── config.module.ts # Configuration module
│ │ └── config.service.ts # Service to access environment variables
│ ├── common/ # Common helpers, decorators, interceptors
│ │ ├── decorators/
│ │ │ └── roles.decorator.ts # Role-based access control decorators
│ │ └── filters/
│ │ └── all-exception.filter.ts # Global exception handling filter
│ ├── app.module.ts # Root application module
│ ├── main.ts # Entry point of the application
├── test/ # Unit and E2E tests
│ ├── auth/
│ │ └── auth.controller.spec.ts # Auth controller test cases
│ ├── transactions/
│ │ └── transactions.controller.spec.ts # Transactions test cases
│ └── app.e2e-spec.ts # End-to-end testing
├── .env # Environment variables (API keys, DB config)
├── nest-cli.json # Nest CLI configuration
├── tsconfig.json # TypeScript configuration
├── package.json # Dependencies and scripts
└── README.md # Project documentation
Breakdown of Key Directories and Files
1. prisma/
-
schema.prisma
: Defines the data models for your project (e.g., users, transactions, budgets, etc.). It is your primary interface for managing database schema. -
migrations/
: Prisma will automatically create migration files in this folder when runningprisma migrate
. -
seed.ts
: A script to seed initial data into your database (e.g., default categories, sample users).
2. src/
- The main source directory where all the business logic, modules, services, controllers, and entities are defined.
3. Modules
a. Authentication (auth/
)
-
auth.controller.ts
: Defines the routes for login, registration, and token refresh. -
auth.service.ts
: Contains business logic for handling user authentication (JWT generation, validation). -
jwt.strategy.ts
: JWT strategy for securing routes and managing authorization.
b. Users (users/
)
-
users.controller.ts
: Handles routes related to user management (e.g., fetching user profiles). -
users.service.ts
: Contains business logic for managing users (CRUD operations). -
create-user.dto.ts
: DTO (Data Transfer Object) for user registration.
c. Transactions (transactions/
)
-
transactions.controller.ts
: Defines routes for managing income and expense transactions (CRUD operations). -
transactions.service.ts
: Business logic for handling transactions, including categorization and totals. -
transaction.entity.ts
: Prisma model for transactions.
d. Budgets (budgets/
)
-
budgets.controller.ts
: Defines routes for budget management (creating and updating budgets). -
budgets.service.ts
: Business logic for tracking budget progress and spending limits. -
budget.entity.ts
: Prisma model for budget data.
e. Reports (reports/
)
-
reports.controller.ts
: Defines routes for generating financial reports. -
reports.service.ts
: Business logic for income and expense analytics, spending patterns, and generating reports.
f. Financial Goals (goals/
)
-
goals.controller.ts
: Handles routes for managing financial goals. -
goals.service.ts
: Business logic for goal tracking and progress updates. -
goal.entity.ts
: Prisma model for financial goals.
g. Notifications (notifications/
)
-
notifications.controller.ts
: Defines routes for sending alerts (e.g., budget threshold alerts, goal progress notifications). -
notifications.service.ts
: Manages sending notifications via email or push notifications.
4. config/
-
config.module.ts
: Centralizes environment configuration, including database connection, JWT secret, and other environment variables. -
config.service.ts
: Provides access to environment variables and configuration throughout the app.
5. common/
- This directory contains reusable decorators, interceptors, and filters.
-
Decorators: Example is the
roles.decorator.ts
for applying role-based access control. -
Filters: Example is the
all-exception.filter.ts
for handling global exceptions and errors.
6. Entry Files
-
app.module.ts
: The root module where all other modules (Auth, Users, Transactions, etc.) are imported. -
main.ts
: The entry point of the application, which starts the NestJS server and applies middleware.
7. test/
- Contains unit tests and end-to-end (E2E) tests for the controllers and services.
- Each module (auth, transactions, etc.) will have its own set of test files.
8. Environment Variables (.env
)
- The
.env
file will hold sensitive configurations like your database URL, JWT secret, and other environment-specific settings.
DATABASE_URL=postgres://user:password@localhost:5432/personal-finance-tracker
JWT_SECRET=your_jwt_secret
Key Features for Each Module:
- Authentication: JWT-based authentication with secure login, registration, and role management.
- Transaction Management: Full CRUD for income and expense transactions with category management.
- Budget Tracking: Users can create, update, and track budgets in different categories. Real-time budget alerts and notifications.
- Financial Goals: Users can set financial goals, track progress, and receive updates or suggestions on how to meet those goals.
- Reports: Generate custom reports with graphs and charts to help users analyze spending patterns.
- Notifications: Email or push notifications for budget alerts, upcoming bills, or goal progress.
This backend structure is well-suited for a Personal Finance Tracker, covering all major aspects like transactions, budgeting, goals, and notifications, while maintaining a clear separation of concerns.
Here’s a full implementation for the components you mentioned, styled with Tailwind CSS.
1. LoginForm.tsx
import { useState } from "react";
const LoginForm = () => {
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
// handle login logic
};
return (
<div className="flex items-center justify-center h-screen bg-gray-100">
<form
onSubmit={handleSubmit}
className="bg-white p-6 rounded-lg shadow-lg w-full max-w-sm"
>
<h2 className="text-2xl font-semibold text-center mb-6">Login</h2>
<div className="mb-4">
<label htmlFor="email" className="block text-gray-700">Email</label>
<input
id="email"
type="email"
className="w-full mt-2 p-2 border border-gray-300 rounded focus:outline-none focus:border-blue-500"
value={email}
onChange={(e) => setEmail(e.target.value)}
/>
</div>
<div className="mb-4">
<label htmlFor="password" className="block text-gray-700">Password</label>
<input
id="password"
type="password"
className="w-full mt-2 p-2 border border-gray-300 rounded focus:outline-none focus:border-blue-500"
value={password}
onChange={(e) => setPassword(e.target.value)}
/>
</div>
<button
type="submit"
className="w-full p-2 bg-blue-500 text-white rounded hover:bg-blue-600 transition"
>
Login
</button>
</form>
</div>
);
};
export default LoginForm;
2. RegisterForm.tsx
import { useState } from "react";
const RegisterForm = () => {
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [confirmPassword, setConfirmPassword] = useState("");
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
// handle registration logic
};
return (
<div className="flex items-center justify-center h-screen bg-gray-100">
<form
onSubmit={handleSubmit}
className="bg-white p-6 rounded-lg shadow-lg w-full max-w-sm"
>
<h2 className="text-2xl font-semibold text-center mb-6">Register</h2>
<div className="mb-4">
<label htmlFor="email" className="block text-gray-700">Email</label>
<input
id="email"
type="email"
className="w-full mt-2 p-2 border border-gray-300 rounded focus:outline-none focus:border-blue-500"
value={email}
onChange={(e) => setEmail(e.target.value)}
/>
</div>
<div className="mb-4">
<label htmlFor="password" className="block text-gray-700">Password</label>
<input
id="password"
type="password"
className="w-full mt-2 p-2 border border-gray-300 rounded focus:outline-none focus:border-blue-500"
value={password}
onChange={(e) => setPassword(e.target.value)}
/>
</div>
<div className="mb-4">
<label htmlFor="confirmPassword" className="block text-gray-700">Confirm Password</label>
<input
id="confirmPassword"
type="password"
className="w-full mt-2 p-2 border border-gray-300 rounded focus:outline-none focus:border-blue-500"
value={confirmPassword}
onChange={(e) => setConfirmPassword(e.target.value)}
/>
</div>
<button
type="submit"
className="w-full p-2 bg-blue-500 text-white rounded hover:bg-blue-600 transition"
>
Register
</button>
</form>
</div>
);
};
export default RegisterForm;
3. BudgetOverview.tsx
const BudgetOverview = () => {
return (
<div className="bg-white p-6 rounded-lg shadow-lg">
<h2 className="text-xl font-semibold mb-4">Budget Overview</h2>
<p>Total Budget: $5,000</p>
<p>Total Spent: $2,500</p>
<p>Remaining: $2,500</p>
</div>
);
};
export default BudgetOverview;
4. ExpenseChart.tsx
For charts, I suggest using a library like Chart.js. Here’s how you could implement an expense chart using react-chartjs-2:
First, install the dependencies:
npm install react-chartjs-2 chart.js
Then create the component:
import { Pie } from "react-chartjs-2";
import { Chart as ChartJS, ArcElement, Tooltip, Legend } from 'chart.js';
ChartJS.register(ArcElement, Tooltip, Legend);
const ExpenseChart = () => {
const data = {
labels: ["Rent", "Groceries", "Entertainment", "Transport", "Miscellaneous"],
datasets: [
{
label: "Expenses",
data: [1200, 300, 200, 150, 50],
backgroundColor: [
"#f87171",
"#60a5fa",
"#fbbf24",
"#34d399",
"#f472b6",
],
},
],
};
return (
<div className="bg-white p-6 rounded-lg shadow-lg">
<h2 className="text-xl font-semibold mb-4">Expense Breakdown</h2>
<Pie data={data} />
</div>
);
};
export default ExpenseChart;
5. IncomeChart.tsx
Similarly, you can create the IncomeChart
component using Chart.js for an income bar chart:
import { Bar } from "react-chartjs-2";
import { Chart as ChartJS, BarElement, CategoryScale, LinearScale, Tooltip, Legend } from 'chart.js';
ChartJS.register(BarElement, CategoryScale, LinearScale, Tooltip, Legend);
const IncomeChart = () => {
const data = {
labels: ["Salary", "Freelance", "Investments"],
datasets: [
{
label: "Income",
data: [4000, 1000, 500],
backgroundColor: "#4ade80",
},
],
};
return (
<div className="bg-white p-6 rounded-lg shadow-lg">
<h2 className="text-xl font-semibold mb-4">Income Overview</h2>
<Bar data={data} />
</div>
);
};
export default IncomeChart;
6. TransactionTable.tsx
const TransactionTable = () => {
const transactions = [
{ id: 1, description: "Groceries", amount: -50, date: "2024-10-01" },
{ id: 2, description: "Salary", amount: 500, date: "2024-10-02" },
{ id: 3, description: "Netflix Subscription", amount: -12, date: "2024-10-03" },
];
return (
<div className="bg-white p-6 rounded-lg shadow-lg">
<h2 className="text-xl font-semibold mb-4">Recent Transactions</h2>
<table className="min-w-full bg-white">
<thead>
<tr>
<th className="py-2 px-4 border-b">Description</th>
<th className="py-2 px-4 border-b">Amount</th>
<th className="py-2 px-4 border-b">Date</th>
</tr>
</thead>
<tbody>
{transactions.map((transaction) => (
<tr key={transaction.id}>
<td className="py-2 px-4 border-b">{transaction.description}</td>
<td className={`py-2 px-4 border-b ${transaction.amount < 0 ? "text-red-500" : "text-green-500"}`}>
{transaction.amount < 0 ? "-" : "+"}${Math.abs(transaction.amount)}
</td>
<td className="py-2 px-4 border-b">{transaction.date}</td>
</tr>
))}
</tbody>
</table>
</div>
);
};
export default TransactionTable;
Tailwind CSS Configuration
Ensure you have Tailwind CSS installed and configured. Here’s how to configure Tailwind in your project:
- Install Tailwind:
npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init
- Configure
tailwind.config.js
:
module.exports = {
content: [
"./src/**/*.{js,jsx,ts
,tsx}",
],
theme: {
extend: {},
},
plugins: [],
}
- Add Tailwind to your
globals.css
file:
@tailwind base;
@tailwind components;
@tailwind utilities;
With this setup, you now have the LoginForm, RegisterForm, BudgetOverview, ExpenseChart, IncomeChart, and TransactionTable components, styled using Tailwind CSS. You can expand and further customize the components as needed for your personal finance tracker project.
Here’s the full code implementation for the components you requested:
1. AddTransactionForm.tsx
import { useState } from "react";
const AddTransactionForm = () => {
const [description, setDescription] = useState("");
const [amount, setAmount] = useState("");
const [date, setDate] = useState("");
const [type, setType] = useState("expense"); // "expense" or "income"
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
// handle transaction submission logic
};
return (
<form onSubmit={handleSubmit} className="bg-white p-6 rounded-lg shadow-lg">
<h2 className="text-2xl font-semibold mb-6">Add Transaction</h2>
<div className="mb-4">
<label className="block text-gray-700">Description</label>
<input
type="text"
className="w-full mt-2 p-2 border border-gray-300 rounded"
value={description}
onChange={(e) => setDescription(e.target.value)}
/>
</div>
<div className="mb-4">
<label className="block text-gray-700">Amount</label>
<input
type="number"
className="w-full mt-2 p-2 border border-gray-300 rounded"
value={amount}
onChange={(e) => setAmount(e.target.value)}
/>
</div>
<div className="mb-4">
<label className="block text-gray-700">Date</label>
<input
type="date"
className="w-full mt-2 p-2 border border-gray-300 rounded"
value={date}
onChange={(e) => setDate(e.target.value)}
/>
</div>
<div className="mb-4">
<label className="block text-gray-700">Type</label>
<select
className="w-full mt-2 p-2 border border-gray-300 rounded"
value={type}
onChange={(e) => setType(e.target.value)}
>
<option value="expense">Expense</option>
<option value="income">Income</option>
</select>
</div>
<button type="submit" className="w-full p-2 bg-blue-500 text-white rounded hover:bg-blue-600 transition">
Add Transaction
</button>
</form>
);
};
export default AddTransactionForm;
2. BudgetForm.tsx
import { useState } from "react";
const BudgetForm = () => {
const [category, setCategory] = useState("");
const [amount, setAmount] = useState("");
const [startDate, setStartDate] = useState("");
const [endDate, setEndDate] = useState("");
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
// handle budget creation logic
};
return (
<form onSubmit={handleSubmit} className="bg-white p-6 rounded-lg shadow-lg">
<h2 className="text-2xl font-semibold mb-6">Create Budget</h2>
<div className="mb-4">
<label className="block text-gray-700">Category</label>
<input
type="text"
className="w-full mt-2 p-2 border border-gray-300 rounded"
value={category}
onChange={(e) => setCategory(e.target.value)}
/>
</div>
<div className="mb-4">
<label className="block text-gray-700">Amount</label>
<input
type="number"
className="w-full mt-2 p-2 border border-gray-300 rounded"
value={amount}
onChange={(e) => setAmount(e.target.value)}
/>
</div>
<div className="mb-4">
<label className="block text-gray-700">Start Date</label>
<input
type="date"
className="w-full mt-2 p-2 border border-gray-300 rounded"
value={startDate}
onChange={(e) => setStartDate(e.target.value)}
/>
</div>
<div className="mb-4">
<label className="block text-gray-700">End Date</label>
<input
type="date"
className="w-full mt-2 p-2 border border-gray-300 rounded"
value={endDate}
onChange={(e) => setEndDate(e.target.value)}
/>
</div>
<button type="submit" className="w-full p-2 bg-blue-500 text-white rounded hover:bg-blue-600 transition">
Create Budget
</button>
</form>
);
};
export default BudgetForm;
3. MainLayout.tsx
import { ReactNode } from "react";
interface MainLayoutProps {
children: ReactNode;
}
const MainLayout = ({ children }: MainLayoutProps) => {
return (
<div className="min-h-screen flex flex-col">
<header className="bg-blue-600 text-white p-4">
<h1 className="text-2xl">Personal Finance Tracker</h1>
</header>
<main className="flex-grow bg-gray-100 p-6">{children}</main>
<footer className="bg-blue-600 text-white p-4 text-center">
<p>© 2024 Personal Finance Tracker</p>
</footer>
</div>
);
};
export default MainLayout;
4. AuthLayout.tsx
import { ReactNode } from "react";
interface AuthLayoutProps {
children: ReactNode;
}
const AuthLayout = ({ children }: AuthLayoutProps) => {
return (
<div className="flex items-center justify-center min-h-screen bg-gray-100">
<div className="w-full max-w-md bg-white p-6 rounded-lg shadow-lg">{children}</div>
</div>
);
};
export default AuthLayout;
5. Button.tsx
interface ButtonProps {
label: string;
onClick: () => void;
type?: "button" | "submit";
}
const Button = ({ label, onClick, type = "button" }: ButtonProps) => {
return (
<button
type={type}
onClick={onClick}
className="bg-blue-500 text-white py-2 px-4 rounded hover:bg-blue-600 transition"
>
{label}
</button>
);
};
export default Button;
6. Modal.tsx
import { ReactNode } from "react";
interface ModalProps {
isOpen: boolean;
onClose: () => void;
children: ReactNode;
}
const Modal = ({ isOpen, onClose, children }: ModalProps) => {
if (!isOpen) return null;
return (
<div className="fixed inset-0 flex items-center justify-center bg-black bg-opacity-50 z-50">
<div className="bg-white p-6 rounded-lg shadow-lg">
<button
className="absolute top-2 right-2 text-gray-600 hover:text-gray-800"
onClick={onClose}
>
×
</button>
{children}
</div>
</div>
);
};
export default Modal;
7. InputField.tsx
interface InputFieldProps {
label: string;
type: string;
value: string;
onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
}
const InputField = ({ label, type, value, onChange }: InputFieldProps) => {
return (
<div className="mb-4">
<label className="block text-gray-700">{label}</label>
<input
type={type}
className="w-full mt-2 p-2 border border-gray-300 rounded"
value={value}
onChange={onChange}
/>
</div>
);
};
export default InputField;
8. BudgetAlert.tsx
interface BudgetAlertProps {
message: string;
onClose: () => void;
}
const BudgetAlert = ({ message, onClose }: BudgetAlertProps) => {
return (
<div className="fixed top-4 right-4 bg-red-500 text-white p-4 rounded-lg shadow-lg">
<p>{message}</p>
<button
className="text-white ml-4"
onClick={onClose}
>
×
</button>
</div>
);
};
export default BudgetAlert;
Summary
-
AddTransactionForm.tsx
andBudgetForm.tsx
provide forms for adding transactions and creating budgets. -
MainLayout.tsx
andAuthLayout.tsx
serve as layouts for authenticated and unauthenticated pages. -
Button.tsx
,Modal.tsx
, andInputField.tsx
are reusable shared components. - **
BudgetAlert.tsx
** is a notification component to alert users about budget-related events.
Each component is styled with Tailwind CSS and is modular, allowing you to easily reuse and expand functionality across your application. Let me know if you need more!
Here’s the full code implementation for the React Context and custom hooks you requested.
1. AuthContext.tsx
This file provides authentication context to manage the user login state and authentication logic.
import { createContext, useContext, useState, ReactNode, useEffect } from "react";
import { useRouter } from "next/router";
// Define the shape of the context
interface AuthContextType {
user: string | null;
login: (email: string, password: string) => Promise<void>;
logout: () => void;
}
// Create the AuthContext
const AuthContext = createContext<AuthContextType | undefined>(undefined);
// AuthProvider component
export const AuthProvider = ({ children }: { children: ReactNode }) => {
const [user, setUser] = useState<string | null>(null);
const router = useRouter();
// Simulate login function
const login = async (email: string, password: string) => {
// Mock login logic (replace with API call)
if (email === "user@example.com" && password === "password") {
setUser(email);
router.push("/dashboard");
} else {
throw new Error("Invalid credentials");
}
};
// Logout function
const logout = () => {
setUser(null);
router.push("/auth/login");
};
useEffect(() => {
// Simulate retrieving logged in user (e.g., from local storage)
const storedUser = localStorage.getItem("user");
if (storedUser) {
setUser(storedUser);
}
}, []);
useEffect(() => {
// Simulate persisting user login state
if (user) {
localStorage.setItem("user", user);
} else {
localStorage.removeItem("user");
}
}, [user]);
return (
<AuthContext.Provider value={{ user, login, logout }}>
{children}
</AuthContext.Provider>
);
};
// Custom hook to use the AuthContext
export const useAuth = () => {
const context = useContext(AuthContext);
if (context === undefined) {
throw new Error("useAuth must be used within an AuthProvider");
}
return context;
};
2. BudgetContext.tsx
This file provides context for managing user budgets across the application.
import { createContext, useContext, useState, ReactNode } from "react";
// Define the shape of the context
interface Budget {
id: string;
category: string;
amount: number;
startDate: string;
endDate: string;
}
interface BudgetContextType {
budgets: Budget[];
addBudget: (budget: Budget) => void;
removeBudget: (id: string) => void;
}
// Create the BudgetContext
const BudgetContext = createContext<BudgetContextType | undefined>(undefined);
// BudgetProvider component
export const BudgetProvider = ({ children }: { children: ReactNode }) => {
const [budgets, setBudgets] = useState<Budget[]>([]);
// Function to add a new budget
const addBudget = (budget: Budget) => {
setBudgets((prevBudgets) => [...prevBudgets, budget]);
};
// Function to remove a budget
const removeBudget = (id: string) => {
setBudgets((prevBudgets) => prevBudgets.filter((budget) => budget.id !== id));
};
return (
<BudgetContext.Provider value={{ budgets, addBudget, removeBudget }}>
{children}
</BudgetContext.Provider>
);
};
// Custom hook to use the BudgetContext
export const useBudgets = () => {
const context = useContext(BudgetContext);
if (context === undefined) {
throw new Error("useBudgets must be used within a BudgetProvider");
}
return context;
};
3. useAuth.tsx
This hook is a convenience wrapper around the AuthContext
for handling authentication logic in components.
import { useAuth } from "../contexts/AuthContext";
export const useAuth = () => {
const { user, login, logout } = useAuth();
const handleLogin = async (email: string, password: string) => {
try {
await login(email, password);
} catch (error) {
console.error("Login failed", error);
}
};
const handleLogout = () => {
logout();
};
return {
user,
handleLogin,
handleLogout,
};
};
4. useTransactions.tsx
This hook provides logic for managing transactions (e.g., adding, removing, fetching).
import { useState } from "react";
// Define transaction type
interface Transaction {
id: string;
description: string;
amount: number;
date: string;
type: "income" | "expense";
}
export const useTransactions = () => {
const [transactions, setTransactions] = useState<Transaction[]>([]);
// Function to add a new transaction
const addTransaction = (transaction: Transaction) => {
setTransactions((prevTransactions) => [...prevTransactions, transaction]);
};
// Function to remove a transaction by ID
const removeTransaction = (id: string) => {
setTransactions((prevTransactions) =>
prevTransactions.filter((transaction) => transaction.id !== id)
);
};
// Simulated fetchTransactions function (replace with API call)
const fetchTransactions = () => {
// Mock transactions data
const mockTransactions: Transaction[] = [
{ id: "1", description: "Groceries", amount: -50, date: "2024-10-01", type: "expense" },
{ id: "2", description: "Salary", amount: 500, date: "2024-10-02", type: "income" },
{ id: "3", description: "Netflix", amount: -12, date: "2024-10-03", type: "expense" },
];
setTransactions(mockTransactions);
};
return {
transactions,
addTransaction,
removeTransaction,
fetchTransactions,
};
};
5. useBudgets.tsx
This hook interacts with the BudgetContext
for managing budgets in your components.
import { useBudgets } from "../contexts/BudgetContext";
export const useBudgets = () => {
const { budgets, addBudget, removeBudget } = useBudgets();
const handleAddBudget = (category: string, amount: number, startDate: string, endDate: string) => {
const newBudget = {
id: Date.now().toString(),
category,
amount,
startDate,
endDate,
};
addBudget(newBudget);
};
const handleRemoveBudget = (id: string) => {
removeBudget(id);
};
return {
budgets,
handleAddBudget,
handleRemoveBudget,
};
};
Explanation of Code
-
AuthContext.tsx
: Manages user authentication state (login/logout) and provides a global authentication context for the app. -
BudgetContext.tsx
: Manages budget state (add, remove) and provides a global budget context for the app. -
useAuth.tsx
: A custom hook that abstracts authentication logic for easier use in components. -
useTransactions.tsx
: A custom hook for managing transactions in your app, including adding/removing transactions and fetching mock data. -
useBudgets.tsx
: A custom hook for managing budgets, allowing components to easily add and remove budgets through theBudgetContext
.
This structure makes it easy to maintain state across your application while keeping logic encapsulated and reusable. Let me know if you need further adjustments or explanations!
Here’s a full implementation for the Next.js pages and routes you requested, covering authentication, the dashboard, goals, transactions, budgets, and the home page.
1. auth/login.tsx
import { useState } from "react";
import { useAuth } from "../../contexts/AuthContext";
const LoginPage = () => {
const { handleLogin } = useAuth();
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [error, setError] = useState("");
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
try {
await handleLogin(email, password);
} catch (err) {
setError("Login failed. Please check your credentials.");
}
};
return (
<div className="flex items-center justify-center h-screen bg-gray-100">
<form onSubmit={handleSubmit} className="bg-white p-6 rounded-lg shadow-lg max-w-sm w-full">
<h2 className="text-2xl font-semibold text-center mb-6">Login</h2>
{error && <p className="text-red-500 text-center mb-4">{error}</p>}
<div className="mb-4">
<label className="block text-gray-700">Email</label>
<input
type="email"
className="w-full mt-2 p-2 border border-gray-300 rounded"
value={email}
onChange={(e) => setEmail(e.target.value)}
/>
</div>
<div className="mb-4">
<label className="block text-gray-700">Password</label>
<input
type="password"
className="w-full mt-2 p-2 border border-gray-300 rounded"
value={password}
onChange={(e) => setPassword(e.target.value)}
/>
</div>
<button type="submit" className="w-full bg-blue-500 text-white py-2 px-4 rounded hover:bg-blue-600 transition">
Login
</button>
</form>
</div>
);
};
export default LoginPage;
2. auth/register.tsx
import { useState } from "react";
import { useAuth } from "../../contexts/AuthContext";
const RegisterPage = () => {
const { handleLogin } = useAuth();
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [confirmPassword, setConfirmPassword] = useState("");
const [error, setError] = useState("");
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
if (password !== confirmPassword) {
setError("Passwords do not match.");
return;
}
try {
await handleLogin(email, password);
} catch (err) {
setError("Registration failed. Please try again.");
}
};
return (
<div className="flex items-center justify-center h-screen bg-gray-100">
<form onSubmit={handleSubmit} className="bg-white p-6 rounded-lg shadow-lg max-w-sm w-full">
<h2 className="text-2xl font-semibold text-center mb-6">Register</h2>
{error && <p className="text-red-500 text-center mb-4">{error}</p>}
<div className="mb-4">
<label className="block text-gray-700">Email</label>
<input
type="email"
className="w-full mt-2 p-2 border border-gray-300 rounded"
value={email}
onChange={(e) => setEmail(e.target.value)}
/>
</div>
<div className="mb-4">
<label className="block text-gray-700">Password</label>
<input
type="password"
className="w-full mt-2 p-2 border border-gray-300 rounded"
value={password}
onChange={(e) => setPassword(e.target.value)}
/>
</div>
<div className="mb-4">
<label className="block text-gray-700">Confirm Password</label>
<input
type="password"
className="w-full mt-2 p-2 border border-gray-300 rounded"
value={confirmPassword}
onChange={(e) => setConfirmPassword(e.target.value)}
/>
</div>
<button type="submit" className="w-full bg-blue-500 text-white py-2 px-4 rounded hover:bg-blue-600 transition">
Register
</button>
</form>
</div>
);
};
export default RegisterPage;
3. dashboard/index.tsx
import BudgetOverview from "../../components/dashboard/BudgetOverview";
import TransactionTable from "../../components/dashboard/TransactionTable";
import ExpenseChart from "../../components/dashboard/ExpenseChart";
import IncomeChart from "../../components/dashboard/IncomeChart";
const DashboardPage = () => {
return (
<div className="p-6 bg-gray-100 min-h-screen">
<h1 className="text-3xl font-semibold mb-6">Dashboard</h1>
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6 mb-6">
<BudgetOverview />
<ExpenseChart />
<IncomeChart />
</div>
<TransactionTable />
</div>
);
};
export default DashboardPage;
4. dashboard/reports.tsx
const ReportsPage = () => {
return (
<div className="p-6 bg-gray-100 min-h-screen">
<h1 className="text-3xl font-semibold mb-6">Reports</h1>
<p>Here you can view detailed financial reports and analysis.</p>
</div>
);
};
export default ReportsPage;
5. goals/create.tsx
import { useState } from "react";
const CreateGoalPage = () => {
const [goal, setGoal] = useState("");
const [amount, setAmount] = useState("");
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
// handle goal creation logic
};
return (
<div className="p-6 bg-gray-100 min-h-screen">
<h1 className="text-3xl font-semibold mb-6">Create Financial Goal</h1>
<form onSubmit={handleSubmit} className="bg-white p-6 rounded-lg shadow-lg max-w-sm w-full">
<div className="mb-4">
<label className="block text-gray-700">Goal</label>
<input
type="text"
className="w-full mt-2 p-2 border border-gray-300 rounded"
value={goal}
onChange={(e) => setGoal(e.target.value)}
/>
</div>
<div className="mb-4">
<label className="block text-gray-700">Amount</label>
<input
type="number"
className="w-full mt-2 p-2 border border-gray-300 rounded"
value={amount}
onChange={(e) => setAmount(e.target.value)}
/>
</div>
<button type="submit" className="w-full bg-blue-500 text-white py-2 px-4 rounded hover:bg-blue-600 transition">
Create Goal
</button>
</form>
</div>
);
};
export default CreateGoalPage;
6. goals/index.tsx
const GoalsPage = () => {
const goals = [
{ id: 1, name: "Save for a new car", amount: 5000 },
{ id: 2, name: "Emergency Fund", amount: 1000 },
];
return (
<div className="p-6 bg-gray-100 min-h-screen">
<h1 className="text-3xl font-semibold mb-6">Financial Goals</h1>
<ul>
{goals.map((goal) => (
<li key={goal.id} className="mb-4 p-4 bg-white rounded-lg shadow-lg">
<p>{goal.name}</p>
<p>Target Amount: ${goal.amount}</p>
</li>
))}
</ul>
</div>
);
};
export default GoalsPage;
7. transactions/add.tsx
import AddTransactionForm from "../../components/forms/AddTransactionForm";
const AddTransactionPage = () => {
return (
<div className="p-6 bg-gray-100 min-h-screen">
<h1 className="text-3xl font-semibold mb-6">Add Transaction</h1>
<AddTransactionForm />
</div>
);
};
export default AddTransactionPage;
8. transactions/index.tsx
const TransactionsPage = () => {
const transactions = [
{ id: 1, description: "Groceries", amount: -50, date: "2024-10-01" },
{ id: 2, description: "Salary", amount: 500, date
: "2024-10-02" },
];
return (
<div className="p-6 bg-gray-100 min-h-screen">
<h1 className="text-3xl font-semibold mb-6">Transactions</h1>
<ul>
{transactions.map((transaction) => (
<li key={transaction.id} className="mb-4 p-4 bg-white rounded-lg shadow-lg">
<p>{transaction.description}</p>
<p>Amount: ${transaction.amount}</p>
<p>Date: {transaction.date}</p>
</li>
))}
</ul>
</div>
);
};
export default TransactionsPage;
9. budgets/create.tsx
import BudgetForm from "../../components/forms/BudgetForm";
const CreateBudgetPage = () => {
return (
<div className="p-6 bg-gray-100 min-h-screen">
<h1 className="text-3xl font-semibold mb-6">Create Budget</h1>
<BudgetForm />
</div>
);
};
export default CreateBudgetPage;
10. budgets/index.tsx
const BudgetsPage = () => {
const budgets = [
{ id: 1, category: "Groceries", amount: 300 },
{ id: 2, category: "Entertainment", amount: 100 },
];
return (
<div className="p-6 bg-gray-100 min-h-screen">
<h1 className="text-3xl font-semibold mb-6">Budgets</h1>
<ul>
{budgets.map((budget) => (
<li key={budget.id} className="mb-4 p-4 bg-white rounded-lg shadow-lg">
<p>Category: {budget.category}</p>
<p>Amount: ${budget.amount}</p>
</li>
))}
</ul>
</div>
);
};
export default BudgetsPage;
11. index.tsx
(Landing Page)
import Link from "next/link";
const HomePage = () => {
return (
<div className="h-screen flex flex-col justify-center items-center bg-gray-100">
<h1 className="text-4xl font-bold mb-6">Welcome to Personal Finance Tracker</h1>
<div className="space-x-4">
<Link href="/auth/login">
<a className="bg-blue-500 text-white py-2 px-4 rounded hover:bg-blue-600 transition">
Login
</a>
</Link>
<Link href="/auth/register">
<a className="bg-green-500 text-white py-2 px-4 rounded hover:bg-green-600 transition">
Register
</a>
</Link>
</div>
</div>
);
};
export default HomePage;
Summary
-
Authentication pages (
login.tsx
,register.tsx
) handle user login and registration. -
Dashboard (
dashboard/index.tsx
) includes components like budget overview, charts, and a transaction table. - Goals pages allow users to create and view financial goals.
- Transaction and Budget pages handle adding and viewing transactions and budgets.
- Landing page offers links to log in or register.
All pages are styled with Tailwind CSS and are ready for Next.js. Let me know if you need further customizations or explanations!
Here’s the full code implementation for the services, Redux slices, styles, utility functions, and types you requested.
1. Services
authService.ts
This service handles authentication-related API calls.
import axios from "axios";
const API_URL = "/api/auth";
export const login = async (email: string, password: string) => {
const response = await axios.post(`${API_URL}/login`, { email, password });
return response.data;
};
export const register = async (email: string, password: string) => {
const response = await axios.post(`${API_URL}/register`, { email, password });
return response.data;
};
export const logout = async () => {
// Handle token removal logic
};
transactionService.ts
This service handles API calls related to transactions.
import axios from "axios";
const API_URL = "/api/transactions";
export const getTransactions = async () => {
const response = await axios.get(API_URL);
return response.data;
};
export const addTransaction = async (transaction: any) => {
const response = await axios.post(API_URL, transaction);
return response.data;
};
export const deleteTransaction = async (id: string) => {
const response = await axios.delete(`${API_URL}/${id}`);
return response.data;
};
budgetService.ts
This service handles API calls related to budgets.
import axios from "axios";
const API_URL = "/api/budgets";
export const getBudgets = async () => {
const response = await axios.get(API_URL);
return response.data;
};
export const createBudget = async (budget: any) => {
const response = await axios.post(API_URL, budget);
return response.data;
};
export const deleteBudget = async (id: string) => {
const response = await axios.delete(`${API_URL}/${id}`);
return response.data;
};
reportService.ts
This service handles API calls related to generating reports.
import axios from "axios";
const API_URL = "/api/reports";
export const getReports = async () => {
const response = await axios.get(API_URL);
return response.data;
};
2. Redux State Management
For state management, I'll use Redux Toolkit for this example.
index.ts
This file configures the Redux store.
import { configureStore } from "@reduxjs/toolkit";
import authReducer from "./authSlice";
import budgetReducer from "./budgetSlice";
const store = configureStore({
reducer: {
auth: authReducer,
budgets: budgetReducer,
},
});
export default store;
export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;
authSlice.ts
This file manages authentication state.
import { createSlice, createAsyncThunk } from "@reduxjs/toolkit";
import { login, register } from "../services/authService";
interface AuthState {
user: string | null;
loading: boolean;
error: string | null;
}
const initialState: AuthState = {
user: null,
loading: false,
error: null,
};
export const loginUser = createAsyncThunk(
"auth/login",
async ({ email, password }: { email: string; password: string }, thunkAPI) => {
try {
const response = await login(email, password);
return response;
} catch (error) {
return thunkAPI.rejectWithValue("Invalid credentials");
}
}
);
export const registerUser = createAsyncThunk(
"auth/register",
async ({ email, password }: { email: string; password: string }, thunkAPI) => {
try {
const response = await register(email, password);
return response;
} catch (error) {
return thunkAPI.rejectWithValue("Registration failed");
}
}
);
const authSlice = createSlice({
name: "auth",
initialState,
reducers: {
logout: (state) => {
state.user = null;
},
},
extraReducers: (builder) => {
builder
.addCase(loginUser.pending, (state) => {
state.loading = true;
state.error = null;
})
.addCase(loginUser.fulfilled, (state, action) => {
state.user = action.payload;
state.loading = false;
})
.addCase(loginUser.rejected, (state, action) => {
state.loading = false;
state.error = action.payload as string;
});
},
});
export const { logout } = authSlice.actions;
export default authSlice.reducer;
budgetSlice.ts
This file manages budget state.
import { createSlice, createAsyncThunk } from "@reduxjs/toolkit";
import { createBudget, getBudgets } from "../services/budgetService";
interface BudgetState {
budgets: any[];
loading: boolean;
error: string | null;
}
const initialState: BudgetState = {
budgets: [],
loading: false,
error: null,
};
export const fetchBudgets = createAsyncThunk("budgets/fetch", async () => {
const response = await getBudgets();
return response;
});
export const addBudget = createAsyncThunk(
"budgets/add",
async (budget: any, thunkAPI) => {
try {
const response = await createBudget(budget);
return response;
} catch (error) {
return thunkAPI.rejectWithValue("Failed to create budget");
}
}
);
const budgetSlice = createSlice({
name: "budgets",
initialState,
reducers: {},
extraReducers: (builder) => {
builder
.addCase(fetchBudgets.pending, (state) => {
state.loading = true;
})
.addCase(fetchBudgets.fulfilled, (state, action) => {
state.budgets = action.payload;
state.loading = false;
})
.addCase(fetchBudgets.rejected, (state, action) => {
state.loading = false;
state.error = action.payload as string;
});
},
});
export default budgetSlice.reducer;
3. Styles
globals.css
This is the global CSS file where Tailwind CSS can be imported.
@tailwind base;
@tailwind components;
@tailwind utilities;
/* Custom global styles */
body {
font-family: 'Inter', sans-serif;
}
tailwind.config.js
This file configures Tailwind CSS.
/** @type {import('tailwindcss').Config} */
module.exports = {
content: [
'./src/**/*.{js,ts,jsx,tsx}',
],
theme: {
extend: {},
},
plugins: [],
};
4. Utility Functions
formatDate.ts
Helper function to format dates.
export const formatDate = (date: string) => {
const options: Intl.DateTimeFormatOptions = { year: "numeric", month: "long", day: "numeric" };
return new Date(date).toLocaleDateString(undefined, options);
};
validateForm.ts
Helper function for form validation.
export const validateForm = (fields: { [key: string]: string }) => {
const errors: { [key: string]: string } = {};
for (const key in fields) {
if (!fields[key]) {
errors[key] = `${key} is required`;
}
}
return errors;
};
constants.ts
Application-wide constants.
export const BUDGET_CATEGORIES = [
"Groceries",
"Entertainment",
"Transportation",
"Housing",
"Healthcare",
"Savings",
];
5. Types
transaction.ts
Types related to transactions.
export interface Transaction {
id: string;
description: string;
amount: number;
date: string;
type: "income" | "expense";
}
budget.ts
Types related to budgets.
export interface Budget {
id: string;
category: string;
amount: number;
startDate: string;
endDate: string;
}
user.ts
Types related to user and authentication.
export interface User {
id: string;
email: string;
token: string;
}
Summary
- Services handle API requests for authentication, transactions, budgets, and reports.
- Redux slices manage application state for authentication and budgets.
- Styles include global CSS and Tailwind configuration for styling.
- Utility functions help with tasks like date formatting and form validation.
- TypeScript types define the structure of transactions, budgets, and users.
This setup provides a solid structure for a scalable and maintainable frontend application. Let me know if you need any further details or customizations!
Here’s the full backend code implementation for your Personal Finance Tracker backend structure using NestJS and Prisma. This implementation will cover authentication, user management, and the Prisma schema setup.
1. Prisma Schema (prisma/schema.prisma
)
This schema defines your database models for users and transactions.
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
model User {
id String @id @default(uuid())
email String @unique
password String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
model Transaction {
id String @id @default(uuid())
description String
amount Float
date DateTime
userId String
user User @relation(fields: [userId], references: [id])
createdAt DateTime @default(now())
}
2. Prisma Seed (prisma/seed.ts
)
This file seeds initial data into the database.
import { PrismaClient } from "@prisma/client";
const prisma = new PrismaClient();
async function main() {
await prisma.user.create({
data: {
email: "admin@example.com",
password: "password123", // In a real-world app, this should be hashed
},
});
}
main()
.catch((e) => {
console.error(e);
process.exit(1);
})
.finally(async () => {
await prisma.$disconnect();
});
3. Authentication Module (src/auth
)
auth.controller.ts
Handles login functionality.
import { Controller, Post, Body, UnauthorizedException } from "@nestjs/common";
import { AuthService } from "./auth.service";
import { LoginDto } from "./dto/login.dto";
@Controller("auth")
export class AuthController {
constructor(private readonly authService: AuthService) {}
@Post("login")
async login(@Body() loginDto: LoginDto) {
const token = await this.authService.validateUser(loginDto);
if (!token) {
throw new UnauthorizedException("Invalid credentials");
}
return { token };
}
}
auth.service.ts
Handles authentication logic and token generation.
import { Injectable } from "@nestjs/common";
import { JwtService } from "@nestjs/jwt";
import { PrismaService } from "../prisma/prisma.service";
import { LoginDto } from "./dto/login.dto";
import * as bcrypt from "bcrypt";
@Injectable()
export class AuthService {
constructor(private prisma: PrismaService, private jwtService: JwtService) {}
async validateUser(loginDto: LoginDto) {
const { email, password } = loginDto;
const user = await this.prisma.user.findUnique({ where: { email } });
if (user && (await bcrypt.compare(password, user.password))) {
const payload = { email: user.email, sub: user.id };
return this.jwtService.sign(payload);
}
return null;
}
}
auth.module.ts
Defines the structure of the auth module.
import { Module } from "@nestjs/common";
import { AuthService } from "./auth.service";
import { AuthController } from "./auth.controller";
import { JwtModule } from "@nestjs/jwt";
import { PassportModule } from "@nestjs/passport";
import { JwtStrategy } from "./jwt.strategy";
import { PrismaModule } from "../prisma/prisma.module";
@Module({
imports: [
PassportModule,
JwtModule.register({
secret: process.env.JWT_SECRET,
signOptions: { expiresIn: "60m" },
}),
PrismaModule,
],
controllers: [AuthController],
providers: [AuthService, JwtStrategy],
})
export class AuthModule {}
jwt.strategy.ts
JWT strategy to secure routes using Passport.js.
import { Injectable } from "@nestjs/common";
import { PassportStrategy } from "@nestjs/passport";
import { ExtractJwt, Strategy } from "passport-jwt";
@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
constructor() {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
ignoreExpiration: false,
secretOrKey: process.env.JWT_SECRET,
});
}
async validate(payload: any) {
return { userId: payload.sub, email: payload.email };
}
}
dto/login.dto.ts
Defines the data transfer object (DTO) for login.
export class LoginDto {
email: string;
password: string;
}
4. User Module (src/users
)
users.controller.ts
Handles user registration and profile management.
import { Controller, Post, Body, Get, Param, UseGuards } from "@nestjs/common";
import { UsersService } from "./users.service";
import { CreateUserDto } from "./dto/create-user.dto";
import { JwtAuthGuard } from "../auth/jwt-auth.guard";
@Controller("users")
export class UsersController {
constructor(private readonly usersService: UsersService) {}
@Post("register")
async register(@Body() createUserDto: CreateUserDto) {
return this.usersService.create(createUserDto);
}
@UseGuards(JwtAuthGuard)
@Get(":id")
async findOne(@Param("id") id: string) {
return this.usersService.findOne(id);
}
}
users.service.ts
Handles business logic for user creation and retrieval.
import { Injectable } from "@nestjs/common";
import { PrismaService } from "../prisma/prisma.service";
import { CreateUserDto } from "./dto/create-user.dto";
import * as bcrypt from "bcrypt";
@Injectable()
export class UsersService {
constructor(private prisma: PrismaService) {}
async create(createUserDto: CreateUserDto) {
const { email, password } = createUserDto;
const hashedPassword = await bcrypt.hash(password, 10);
return this.prisma.user.create({
data: {
email,
password: hashedPassword,
},
});
}
async findOne(id: string) {
return this.prisma.user.findUnique({
where: { id },
});
}
}
users.module.ts
Defines the structure of the user module.
import { Module } from "@nestjs/common";
import { UsersService } from "./users.service";
import { UsersController } from "./users.controller";
import { PrismaModule } from "../prisma/prisma.module";
@Module({
imports: [PrismaModule],
controllers: [UsersController],
providers: [UsersService],
})
export class UsersModule {}
dto/create-user.dto.ts
Defines the DTO for creating a user.
export class CreateUserDto {
email: string;
password: string;
}
5. Prisma Service (src/prisma/prisma.service.ts
)
This file provides the Prisma Client and is used across different modules.
import { Injectable, OnModuleInit, OnModuleDestroy } from "@nestjs/common";
import { PrismaClient } from "@prisma/client";
@Injectable()
export class PrismaService extends PrismaClient implements OnModuleInit, OnModuleDestroy {
async onModuleInit() {
await this.$connect();
}
async onModuleDestroy() {
await this.$disconnect();
}
}
prisma.module.ts
This module provides the Prisma service to other modules.
import { Module } from "@nestjs/common";
import { PrismaService } from "./prisma.service";
@Module({
providers: [PrismaService],
exports: [PrismaService],
})
export class PrismaModule {}
6. JWT Auth Guard (src/auth/jwt-auth.guard.ts
)
This guard secures endpoints by checking if the user is authenticated.
import { Injectable, CanActivate, ExecutionContext } from "@nestjs/common";
import { AuthGuard } from "@nestjs/passport";
@Injectable()
export class JwtAuthGuard extends AuthGuard("jwt") implements CanActivate {
canActivate(context: ExecutionContext) {
return super.canActivate(context);
}
}
7. Environment Variables
Ensure you have a .env
file with the following content:
DATABASE_URL=postgresql://user:password@localhost:5432/personal_finance_tracker
JWT_SECRET=your_jwt_secret
8. App Module (src/app.module.ts
)
This is the main module that ties all other modules together.
import { Module } from "@nestjs/common";
import { AuthModule } from "./auth/auth.module";
import { UsersModule } from "./users/users.module";
import { PrismaModule } from "./prisma/prisma.module";
@Module({
imports: [AuthModule, UsersModule, PrismaModule],
})
export class AppModule {}
9. Main File (src/main.ts
)
This is the entry point of the application.
import { NestFactory } from "@nestjs/core";
import { AppModule } from "./app.module";
import { ValidationPipe } from "@nestjs/common";
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.useGlobalPipes(new ValidationPipe());
await app.listen(3000);
}
bootstrap();
Summary
This setup includes:
- Prisma ORM for database interactions.
- Authentication module with JWT and login functionality.
- **Users
** module for registration and user management.
- DTOs for validating incoming data and ensuring clean API requests.
- Prisma service to handle database interactions globally.
To set up this project:
- Create a PostgreSQL database.
- Run
prisma migrate dev
to generate the migration files. - Run
prisma db seed
to seed initial data. - Use
npm run start
to run the NestJS backend.
Let me know if you need any additional features!
Here is the full implementation for the Transactions and Budgets modules, including the controllers, services, DTOs, and entities.
1. Transactions Module (src/transactions
)
transactions.controller.ts
This file handles all API endpoints related to transactions.
import { Controller, Get, Post, Body, Patch, Param, Delete, UseGuards } from "@nestjs/common";
import { TransactionsService } from "./transactions.service";
import { CreateTransactionDto } from "./dto/create-transaction.dto";
import { UpdateTransactionDto } from "./dto/update-transaction.dto";
import { JwtAuthGuard } from "../auth/jwt-auth.guard";
@Controller("transactions")
@UseGuards(JwtAuthGuard)
export class TransactionsController {
constructor(private readonly transactionsService: TransactionsService) {}
@Post()
create(@Body() createTransactionDto: CreateTransactionDto) {
return this.transactionsService.create(createTransactionDto);
}
@Get()
findAll() {
return this.transactionsService.findAll();
}
@Get(":id")
findOne(@Param("id") id: string) {
return this.transactionsService.findOne(id);
}
@Patch(":id")
update(@Param("id") id: string, @Body() updateTransactionDto: UpdateTransactionDto) {
return this.transactionsService.update(id, updateTransactionDto);
}
@Delete(":id")
remove(@Param("id") id: string) {
return this.transactionsService.remove(id);
}
}
transactions.service.ts
This file contains the business logic for transactions.
import { Injectable } from "@nestjs/common";
import { PrismaService } from "../prisma/prisma.service";
import { CreateTransactionDto } from "./dto/create-transaction.dto";
import { UpdateTransactionDto } from "./dto/update-transaction.dto";
@Injectable()
export class TransactionsService {
constructor(private prisma: PrismaService) {}
async create(createTransactionDto: CreateTransactionDto) {
return this.prisma.transaction.create({
data: {
...createTransactionDto,
},
});
}
async findAll() {
return this.prisma.transaction.findMany();
}
async findOne(id: string) {
return this.prisma.transaction.findUnique({ where: { id } });
}
async update(id: string, updateTransactionDto: UpdateTransactionDto) {
return this.prisma.transaction.update({
where: { id },
data: updateTransactionDto,
});
}
async remove(id: string) {
return this.prisma.transaction.delete({ where: { id } });
}
}
transactions.module.ts
Defines the structure of the transactions module.
import { Module } from "@nestjs/common";
import { TransactionsService } from "./transactions.service";
import { TransactionsController } from "./transactions.controller";
import { PrismaModule } from "../prisma/prisma.module";
@Module({
imports: [PrismaModule],
controllers: [TransactionsController],
providers: [TransactionsService],
})
export class TransactionsModule {}
dto/create-transaction.dto.ts
Defines the DTO for creating a transaction.
export class CreateTransactionDto {
description: string;
amount: number;
date: string;
userId: string; // Foreign key referencing user
}
dto/update-transaction.dto.ts
Defines the DTO for updating a transaction.
export class UpdateTransactionDto {
description?: string;
amount?: number;
date?: string;
}
entities/transaction.entity.ts
Defines the transaction entity.
import { User } from "../../users/entities/user.entity";
export class Transaction {
id: string;
description: string;
amount: number;
date: Date;
userId: string;
user: User;
}
2. Budgets Module (src/budgets
)
budgets.controller.ts
Handles all API endpoints related to budgets.
import { Controller, Get, Post, Body, Patch, Param, Delete, UseGuards } from "@nestjs/common";
import { BudgetsService } from "./budgets.service";
import { CreateBudgetDto } from "./dto/create-budget.dto";
import { UpdateBudgetDto } from "./dto/update-budget.dto";
import { JwtAuthGuard } from "../auth/jwt-auth.guard";
@Controller("budgets")
@UseGuards(JwtAuthGuard)
export class BudgetsController {
constructor(private readonly budgetsService: BudgetsService) {}
@Post()
create(@Body() createBudgetDto: CreateBudgetDto) {
return this.budgetsService.create(createBudgetDto);
}
@Get()
findAll() {
return this.budgetsService.findAll();
}
@Get(":id")
findOne(@Param("id") id: string) {
return this.budgetsService.findOne(id);
}
@Patch(":id")
update(@Param("id") id: string, @Body() updateBudgetDto: UpdateBudgetDto) {
return this.budgetsService.update(id, updateBudgetDto);
}
@Delete(":id")
remove(@Param("id") id: string) {
return this.budgetsService.remove(id);
}
}
budgets.service.ts
Contains business logic for budgets.
import { Injectable } from "@nestjs/common";
import { PrismaService } from "../prisma/prisma.service";
import { CreateBudgetDto } from "./dto/create-budget.dto";
import { UpdateBudgetDto } from "./dto/update-budget.dto";
@Injectable()
export class BudgetsService {
constructor(private prisma: PrismaService) {}
async create(createBudgetDto: CreateBudgetDto) {
return this.prisma.budget.create({
data: {
...createBudgetDto,
},
});
}
async findAll() {
return this.prisma.budget.findMany();
}
async findOne(id: string) {
return this.prisma.budget.findUnique({ where: { id } });
}
async update(id: string, updateBudgetDto: UpdateBudgetDto) {
return this.prisma.budget.update({
where: { id },
data: updateBudgetDto,
});
}
async remove(id: string) {
return this.prisma.budget.delete({ where: { id } });
}
}
budgets.module.ts
Defines the structure of the budgets module.
import { Module } from "@nestjs/common";
import { BudgetsService } from "./budgets.service";
import { BudgetsController } from "./budgets.controller";
import { PrismaModule } from "../prisma/prisma.module";
@Module({
imports: [PrismaModule],
controllers: [BudgetsController],
providers: [BudgetsService],
})
export class BudgetsModule {}
dto/create-budget.dto.ts
Defines the DTO for creating a budget.
export class CreateBudgetDto {
category: string;
amount: number;
startDate: string;
endDate: string;
userId: string; // Foreign key referencing user
}
dto/update-budget.dto.ts
Defines the DTO for updating a budget.
export class UpdateBudgetDto {
category?: string;
amount?: number;
startDate?: string;
endDate?: string;
}
entities/budget.entity.ts
Defines the budget entity.
import { User } from "../../users/entities/user.entity";
export class Budget {
id: string;
category: string;
amount: number;
startDate: Date;
endDate: Date;
userId: string;
user: User;
}
3. Prisma Schema Update (prisma/schema.prisma
)
Make sure your Prisma schema reflects the Transaction
and Budget
models.
model Transaction {
id String @id @default(uuid())
description String
amount Float
date DateTime
userId String
user User @relation(fields: [userId], references: [id])
createdAt DateTime @default(now())
}
model Budget {
id String @id @default(uuid())
category String
amount Float
startDate DateTime
endDate DateTime
userId String
user User @relation(fields: [userId], references: [id])
createdAt DateTime @default(now())
}
model User {
id String @id @default(uuid())
email String @unique
password String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
transactions Transaction[]
budgets Budget[]
}
Summary
This backend implementation for transactions and budgets includes:
- API controllers: To handle HTTP requests for CRUD operations on transactions and budgets.
- Services: Contains business logic for creating, updating, deleting, and retrieving data from the database.
- DTOs: To validate and transfer data between API requests and the database.
- Entities: Represent the structure of the data in the database.
To run this project:
- Update your Prisma schema and run
prisma migrate dev
to generate the migrations. - Use
npm run start
to run the NestJS backend.
Let me know if you need more customizations or additional features!
Here is the full code implementation for the Reports and Financial Goals modules, including controllers, services, DTOs, and entities.
1. Reports Module (src/reports
)
reports.controller.ts
This controller handles API requests for generating reports.
import { Controller, Get, Query, UseGuards } from "@nestjs/common";
import { ReportsService } from "./reports.service";
import { JwtAuthGuard } from "../auth/jwt-auth.guard";
@Controller("reports")
@UseGuards(JwtAuthGuard)
export class ReportsController {
constructor(private readonly reportsService: ReportsService) {}
@Get("summary")
async getSummaryReport(@Query("userId") userId: string) {
return this.reportsService.generateSummaryReport(userId);
}
@Get("monthly")
async getMonthlyReport(@Query("userId") userId: string, @Query("month") month: string) {
return this.reportsService.generateMonthlyReport(userId, month);
}
}
reports.service.ts
This service handles the business logic for generating reports.
import { Injectable } from "@nestjs/common";
import { PrismaService } from "../prisma/prisma.service";
@Injectable()
export class ReportsService {
constructor(private prisma: PrismaService) {}
async generateSummaryReport(userId: string) {
const totalIncome = await this.prisma.transaction.aggregate({
where: { userId, amount: { gt: 0 } },
_sum: { amount: true },
});
const totalExpenses = await this.prisma.transaction.aggregate({
where: { userId, amount: { lt: 0 } },
_sum: { amount: true },
});
return {
totalIncome: totalIncome._sum.amount || 0,
totalExpenses: totalExpenses._sum.amount || 0,
netIncome: (totalIncome._sum.amount || 0) + (totalExpenses._sum.amount || 0),
};
}
async generateMonthlyReport(userId: string, month: string) {
const startDate = new Date(`${month}-01`);
const endDate = new Date(`${month}-31`);
const monthlyIncome = await this.prisma.transaction.aggregate({
where: {
userId,
amount: { gt: 0 },
date: { gte: startDate, lte: endDate },
},
_sum: { amount: true },
});
const monthlyExpenses = await this.prisma.transaction.aggregate({
where: {
userId,
amount: { lt: 0 },
date: { gte: startDate, lte: endDate },
},
_sum: { amount: true },
});
return {
monthlyIncome: monthlyIncome._sum.amount || 0,
monthlyExpenses: monthlyExpenses._sum.amount || 0,
netIncome: (monthlyIncome._sum.amount || 0) + (monthlyExpenses._sum.amount || 0),
};
}
}
reports.module.ts
This file defines the structure of the reports module.
import { Module } from "@nestjs/common";
import { ReportsService } from "./reports.service";
import { ReportsController } from "./reports.controller";
import { PrismaModule } from "../prisma/prisma.module";
@Module({
imports: [PrismaModule],
controllers: [ReportsController],
providers: [ReportsService],
})
export class ReportsModule {}
2. Financial Goals Module (src/goals
)
goals.controller.ts
This controller handles API requests related to financial goals.
import { Controller, Get, Post, Patch, Delete, Param, Body, UseGuards } from "@nestjs/common";
import { GoalsService } from "./goals.service";
import { CreateGoalDto } from "./dto/create-goal.dto";
import { UpdateGoalDto } from "./dto/update-goal.dto";
import { JwtAuthGuard } from "../auth/jwt-auth.guard";
@Controller("goals")
@UseGuards(JwtAuthGuard)
export class GoalsController {
constructor(private readonly goalsService: GoalsService) {}
@Post()
async create(@Body() createGoalDto: CreateGoalDto) {
return this.goalsService.create(createGoalDto);
}
@Get()
async findAll(@Param("userId") userId: string) {
return this.goalsService.findAll(userId);
}
@Patch(":id")
async update(@Param("id") id: string, @Body() updateGoalDto: UpdateGoalDto) {
return this.goalsService.update(id, updateGoalDto);
}
@Delete(":id")
async remove(@Param("id") id: string) {
return this.goalsService.remove(id);
}
}
goals.service.ts
This service handles the business logic for managing financial goals.
import { Injectable } from "@nestjs/common";
import { PrismaService } from "../prisma/prisma.service";
import { CreateGoalDto } from "./dto/create-goal.dto";
import { UpdateGoalDto } from "./dto/update-goal.dto";
@Injectable()
export class GoalsService {
constructor(private prisma: PrismaService) {}
async create(createGoalDto: CreateGoalDto) {
return this.prisma.goal.create({
data: {
...createGoalDto,
},
});
}
async findAll(userId: string) {
return this.prisma.goal.findMany({
where: { userId },
});
}
async update(id: string, updateGoalDto: UpdateGoalDto) {
return this.prisma.goal.update({
where: { id },
data: updateGoalDto,
});
}
async remove(id: string) {
return this.prisma.goal.delete({
where: { id },
});
}
}
goals.module.ts
This file defines the structure of the financial goals module.
import { Module } from "@nestjs/common";
import { GoalsService } from "./goals.service";
import { GoalsController } from "./goals.controller";
import { PrismaModule } from "../prisma/prisma.module";
@Module({
imports: [PrismaModule],
controllers: [GoalsController],
providers: [GoalsService],
})
export class GoalsModule {}
dto/create-goal.dto.ts
Defines the DTO for creating a financial goal.
export class CreateGoalDto {
userId: string; // Foreign key referencing user
name: string;
targetAmount: number;
currentAmount: number;
deadline: Date;
}
dto/update-goal.dto.ts
Defines the DTO for updating a financial goal.
export class UpdateGoalDto {
name?: string;
targetAmount?: number;
currentAmount?: number;
deadline?: Date;
}
entities/goal.entity.ts
Defines the goal entity.
import { User } from "../../users/entities/user.entity";
export class Goal {
id: string;
name: string;
targetAmount: number;
currentAmount: number;
deadline: Date;
userId: string;
user: User;
}
3. Prisma Schema Update (prisma/schema.prisma
)
Ensure your Prisma schema reflects the Goal
model.
model Goal {
id String @id @default(uuid())
name String
targetAmount Float
currentAmount Float
deadline DateTime
userId String
user User @relation(fields: [userId], references: [id])
createdAt DateTime @default(now())
}
model User {
id String @id @default(uuid())
email String @unique
password String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
transactions Transaction[]
budgets Budget[]
goals Goal[]
}
After updating the schema, make sure to run:
npx prisma migrate dev --name add-goals
Summary
This implementation includes:
- Reports module with endpoints for generating summary and monthly reports.
- Financial goals module with CRUD operations for managing user financial goals.
- DTOs to handle validation and structuring of data between API requests and the database.
- Entities that represent data models for financial goals and reports.
To run this project:
- Update your Prisma schema and run
npx prisma migrate dev
to apply the new database migrations. - Use
npm run start
to start the NestJS backend.
Let me know if you need more details or further assistance!
Here’s the complete code implementation for the Notifications, Config, Common, and App Module in your NestJS backend.
1. Notifications Module (src/notifications
)
notifications.controller.ts
This file handles API endpoints for sending notifications.
import { Controller, Post, Body, UseGuards } from "@nestjs/common";
import { NotificationsService } from "./notifications.service";
import { JwtAuthGuard } from "../auth/jwt-auth.guard";
@Controller("notifications")
@UseGuards(JwtAuthGuard)
export class NotificationsController {
constructor(private readonly notificationsService: NotificationsService) {}
@Post("send")
async sendNotification(@Body() { userId, message }: { userId: string; message: string }) {
return this.notificationsService.sendNotification(userId, message);
}
}
notifications.service.ts
This file contains business logic for sending notifications. You can expand this to support email, SMS, or push notifications.
import { Injectable } from "@nestjs/common";
import { PrismaService } from "../prisma/prisma.service";
@Injectable()
export class NotificationsService {
constructor(private prisma: PrismaService) {}
async sendNotification(userId: string, message: string) {
// Mock notification logic
console.log(`Notification sent to user ${userId}: ${message}`);
// Log the notification in the database
return this.prisma.notification.create({
data: {
userId,
message,
},
});
}
}
notifications.module.ts
This file defines the structure of the notifications module.
import { Module } from "@nestjs/common";
import { NotificationsService } from "./notifications.service";
import { NotificationsController } from "./notifications.controller";
import { PrismaModule } from "../prisma/prisma.module";
@Module({
imports: [PrismaModule],
controllers: [NotificationsController],
providers: [NotificationsService],
})
export class NotificationsModule {}
2. Config Module (src/config
)
config.module.ts
This module provides a centralized configuration service to access environment variables.
import { Module } from "@nestjs/common";
import { ConfigService } from "./config.service";
@Module({
providers: [ConfigService],
exports: [ConfigService],
})
export class ConfigModule {}
config.service.ts
This service provides a way to access environment variables.
import { Injectable } from "@nestjs/common";
import * as dotenv from "dotenv";
dotenv.config();
@Injectable()
export class ConfigService {
get(key: string): string {
return process.env[key];
}
getDatabaseUrl(): string {
return this.get("DATABASE_URL");
}
getJwtSecret(): string {
return this.get("JWT_SECRET");
}
getPort(): number {
return parseInt(this.get("PORT")) || 3000;
}
}
3. Common Module (src/common
)
decorators/roles.decorator.ts
This file provides a role-based access control decorator.
import { SetMetadata } from "@nestjs/common";
export const ROLES_KEY = "roles";
export const Roles = (...roles: string[]) => SetMetadata(ROLES_KEY, roles);
filters/all-exception.filter.ts
This file handles global exception filtering and can be used for better error handling across the application.
import {
ExceptionFilter,
Catch,
ArgumentsHost,
HttpException,
HttpStatus,
} from "@nestjs/common";
import { Request, Response } from "express";
@Catch()
export class AllExceptionsFilter implements ExceptionFilter {
catch(exception: unknown, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const response = ctx.getResponse<Response>();
const request = ctx.getRequest<Request>();
const status =
exception instanceof HttpException
? exception.getStatus()
: HttpStatus.INTERNAL_SERVER_ERROR;
const message =
exception instanceof HttpException ? exception.getResponse() : "Internal server error";
response.status(status).json({
statusCode: status,
timestamp: new Date().toISOString(),
path: request.url,
message,
});
}
}
4. Root App Module (src/app.module.ts
)
The App Module ties everything together, importing all the modules.
import { Module } from "@nestjs/common";
import { AuthModule } from "./auth/auth.module";
import { UsersModule } from "./users/users.module";
import { PrismaModule } from "./prisma/prisma.module";
import { TransactionsModule } from "./transactions/transactions.module";
import { BudgetsModule } from "./budgets/budgets.module";
import { ReportsModule } from "./reports/reports.module";
import { GoalsModule } from "./goals/goals.module";
import { NotificationsModule } from "./notifications/notifications.module";
import { ConfigModule } from "./config/config.module";
import { APP_FILTER } from "@nestjs/core";
import { AllExceptionsFilter } from "./common/filters/all-exception.filter";
@Module({
imports: [
AuthModule,
UsersModule,
PrismaModule,
TransactionsModule,
BudgetsModule,
ReportsModule,
GoalsModule,
NotificationsModule,
ConfigModule,
],
providers: [
{
provide: APP_FILTER,
useClass: AllExceptionsFilter,
},
],
})
export class AppModule {}
5. Main Entry Point (src/main.ts
)
This is the entry point of the application. It starts the server and applies global configurations like exception filters and validation pipes.
import { NestFactory } from "@nestjs/core";
import { AppModule } from "./app.module";
import { ValidationPipe } from "@nestjs/common";
import { AllExceptionsFilter } from "./common/filters/all-exception.filter";
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.useGlobalPipes(new ValidationPipe());
app.useGlobalFilters(new AllExceptionsFilter());
await app.listen(3000);
}
bootstrap();
6. Notification Entity (Optional)
If you want to store notifications in the database, here’s an optional notification entity for Prisma.
Update schema.prisma
Add this to your Prisma schema:
model Notification {
id String @id @default(uuid())
userId String
user User @relation(fields: [userId], references: [id])
message String
createdAt DateTime @default(now())
}
After updating the schema, run:
npx prisma migrate dev --name add-notifications
Summary
- Notifications Module: Handles sending notifications and can be extended to support various notification channels (e.g., email, SMS, push).
- Config Module: Centralizes environment variable management.
- Common Module: Includes a global exception filter and role-based access decorators.
- App Module: Imports all necessary modules and applies global settings.
- Main Entry Point: Configures the application, initializes global filters and validation, and starts the server.
This structure provides a solid foundation for a maintainable and scalable NestJS backend. Let me know if you need further details or modifications!
If you enjoy my content and would like to support my work, you can buy me a coffee. Your support is greatly appreciated!
Disclaimer: This content is generated by AI.
Top comments (0)