ASP.NET Core  

Expense Tracker — ASP.NET Core + Angular

Introduction

This article shows how to build a production-minded Expense Tracker web application using ASP.NET Core (backend) and Angular (frontend). The app supports:

  • User authentication (JWT)

  • CRUD for expenses and categories

  • Monthly summaries and charts

  • Recurring expenses and simple budget alerts

  • Export to CSV and basic reports

I use simple Indian English, practical code examples, and Angular-focused implementation details. You can copy-paste code and adapt it for your organisation.

High-level architecture

[Angular SPA] <---HTTPS/JWT---> [ASP.NET Core Web API] <----> [SQL Server]
        |                                         |
        +---(optional SignalR for live updates)---+
        +---(blob storage for receipts & exports)-->[Blob Storage]

Frontend responsibilities: user UI, charts, client-side validation, and calling APIs. Backend responsibilities: authentication, business rules, data persistence, export generation, and scheduled jobs for recurring expenses.

Database design (simple and extensible)

Main tables:

  • Users(Id, UserName, Email, PasswordHash, CreatedAt)

  • Categories(Id, UserId, Name, Type) — Type = Expense or Income

  • Expenses(Id, UserId, CategoryId, Amount, Currency, Date, Description, IsRecurring, RecurrenceRule, ReceiptUrl, CreatedAt)

  • Budgets(Id, UserId, CategoryId, Month, Year, LimitAmount)

Notes

  • Use UserId for multi-user separation.

  • RecurrenceRule can store a simple cron-like string or RFC 5545 recurrence rule for future enhancements.

  • Index UserId, Date, CategoryId for fast reports.

ERD (text)

Users (1) --- (M) Categories
Users (1) --- (M) Expenses
Categories (1) --- (M) Expenses
Users (1) --- (M) Budgets

Backend: Project setup and packages

Create project:

dotnet new webapi -n ExpenseBackend
cd ExpenseBackend

Recommended packages:

dotnet add package Microsoft.EntityFrameworkCore.SqlServer
dotnet add package Microsoft.EntityFrameworkCore.Design
dotnet add package Microsoft.AspNetCore.Authentication.JwtBearer
dotnet add package AutoMapper.Extensions.Microsoft.DependencyInjection

Create ExpenseDbContext with DbSet<User>, DbSet<Category>, DbSet<Expense>, DbSet<Budget>.

Backend: EF Core models (simplified)

User.cs

public class User
{
    public Guid Id { get; set; }
    public string UserName { get; set; } = string.Empty;
    public string Email { get; set; } = string.Empty;
    public string PasswordHash { get; set; } = string.Empty;
    public DateTimeOffset CreatedAt { get; set; } = DateTimeOffset.UtcNow;
}

Category.cs

public class Category
{
    public Guid Id { get; set; }
    public Guid UserId { get; set; }
    public string Name { get; set; } = string.Empty;
    public string Type { get; set; } = "Expense"; // or "Income"
}

Expense.cs

public class Expense
{
    public Guid Id { get; set; }
    public Guid UserId { get; set; }
    public Guid CategoryId { get; set; }
    public decimal Amount { get; set; }
    public string Currency { get; set; } = "INR";
    public DateTime Date { get; set; }
    public string? Description { get; set; }
    public bool IsRecurring { get; set; }
    public string? RecurrenceRule { get; set; }
    public string? ReceiptUrl { get; set; }
    public DateTimeOffset CreatedAt { get; set; } = DateTimeOffset.UtcNow;
}

Budget.cs

public class Budget
{
    public Guid Id { get; set; }
    public Guid UserId { get; set; }
    public Guid? CategoryId { get; set; }
    public int Month { get; set; }
    public int Year { get; set; }
    public decimal LimitAmount { get; set; }
}

Backend: DTOs and services

Keep controllers thin and business logic in services. Use DTOs for API contracts.

ExpenseCreateDto

public class ExpenseCreateDto { public Guid CategoryId; public decimal Amount; public string Currency; public DateTime Date; public string? Description; public bool IsRecurring; public string? RecurrenceRule; }

IExpenseService

public interface IExpenseService
{
    Task<ExpenseDto> CreateExpenseAsync(Guid userId, ExpenseCreateDto dto);
    Task<PagedResult<ExpenseDto>> GetExpensesAsync(Guid userId, ExpenseQuery query);
    Task<SummaryDto> GetMonthlySummary(Guid userId, int month, int year);
    Task ExportCsvAsync(Guid userId, ExpenseQuery query, Stream output);
}

Implementation notes:

  • For GetMonthlySummary, use server-side aggregation grouped by category.

  • For ExportCsvAsync stream CSV to avoid memory pressure.

Backend: Authentication (JWT)

Use JWT for API and Angular to store token in memory or secure storage. Example Program.cs snippet:

builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddJwtBearer(options => {
        options.TokenValidationParameters = new TokenValidationParameters { /* issuer, key, audience */ };
    });
app.UseAuthentication();
app.UseAuthorization();

Expose /api/auth/login and /api/auth/register endpoints. On login return { token: "..." }.

Security tips:

  • Use HTTPS always.

  • Use short-lived tokens and refresh tokens.

  • Hash passwords with a secure algorithm (use ASP.NET Identity or Argon2/Bcrypt).

Backend: Monthly summary query example (EF Core)

public async Task<SummaryDto> GetMonthlySummary(Guid userId, int month, int year)
{
    var start = new DateTime(year, month, 1);
    var end = start.AddMonths(1);

    var items = await _db.Expenses
      .Where(e => e.UserId == userId && e.Date >= start && e.Date < end)
      .GroupBy(e => e.CategoryId)
      .Select(g => new { CategoryId = g.Key, Total = g.Sum(x => x.Amount) })
      .ToListAsync();

    // map category names
    return new SummaryDto { Totals = items.Select(i => new CategoryTotal { CategoryId = i.CategoryId, Total = i.Total }).ToList() };
}

Frontend: Angular setup and libraries

Create Angular project and install helpful libraries:

ng new expense-tracker
cd expense-tracker
npm install chart.js ngx-chartjs @auth0/angular-jwt file-saver

Use ngx-chartjs or ng2-charts to render charts. Use @auth0/angular-jwt for token helpers.

Project structure suggestion

src/app/core (auth, http interceptor)
src/app/shared (models, components)
src/app/expenses (list, edit, service)
src/app/dashboard (summary, charts)

Frontend: AuthService and interceptor

auth.service.ts provides login/register and token storage.

auth.interceptor.ts attaches token to each request.

Important: store token in memory or localStorage depending on threat model. For production consider short-lived tokens with refresh flow.

Frontend: ExpenseService (detailed)

expense.service.ts handles API calls and exposes Observables for updates.

@Injectable({providedIn: 'root'})
export class ExpenseService {
  private base = '/api/expenses';

  constructor(private http: HttpClient) {}

  getExpenses(query: any) { return this.http.get<PagedResult<Expense>>(`${this.base}`, { params: query }); }
  createExpense(dto: any) { return this.http.post<Expense>(this.base, dto); }
  updateExpense(id: string, dto: any) { return this.http.put(`${this.base}/${id}`, dto); }
  deleteExpense(id: string) { return this.http.delete(`${this.base}/${id}`); }
  getMonthlySummary(month: number, year: number) { return this.http.get<Summary>(`${this.base}/summary`, { params: { month, year } }); }
  exportCsv(query: any) { return this.http.get(`${this.base}/export`, { params: query, responseType: 'blob' }); }
}

Frontend: Expense list and editor component

list component shows pagination, filters, and actions. Use server-side pagination.

editor component can be a reactive form with fields: amount, category, date, description, receipt (file input), isRecurring.

File upload: upload receipts to /api/expenses/{id}/receipt using FormData and show preview after upload.

Frontend: Dashboard with charts

Create dashboard.component.ts that calls getMonthlySummary and renders:

  • Pie chart of spending by category

  • Line chart of daily spending for month

Example using Chart.js via wrapper

this.expenseService.getMonthlySummary(month, year).subscribe(data => {
  this.pieData = data.totals.map(t => t.total);
  this.pieLabels = data.totals.map(t => t.categoryName);
});

Chart best practices:

  • Limit points on line chart (use daily totals, not every transaction)

  • Use tooltips for amounts

  • Avoid heavy client-side computation on large datasets; aggregate on server.

Recurring expenses and scheduler

Store recurrence metadata in Expense.RecurrenceRule. Use a background job (Hangfire, Quartz.NET, or Azure Functions Timer) to create instances for next period.

Example schedule using Hangfire:

  • Daily job runs and checks Expenses with IsRecurring=true and creates new Expense records when due.

Budget alerts and notifications

For each budget, calculate total spent and compare with LimitAmount. Send notifications when > 80% and 100%.

Notification options:

  • Email using SMTP

  • Push notifications via Web Push or SignalR for live dashboards

Export CSV and reports

Implement streaming CSV on server to avoid OOM. Example using CsvHelper:

public async Task ExportCsvAsync(Guid userId, ExpenseQuery q, Stream output)
{
    var expenses = GetQuery(userId, q).AsNoTracking().AsAsyncEnumerable();
    using var writer = new StreamWriter(output);
    using var csv = new CsvWriter(writer, CultureInfo.InvariantCulture);
    await csv.WriteRecordsAsync(expenses);
}

On client call expenseService.exportCsv(...) and use FileSaver.saveAs to prompt download.

Security and production best practices

  • Always use HTTPS.

  • Validate inputs server-side and enforce authorization per UserId.

  • Use parameterized queries / EF Core to avoid SQL injection.

  • Rate limit export and heavy endpoints.

  • Sanitize any HTML or user-supplied content.

  • Properly configure CORS for your SPA origin.

  • Use centralized logging, structured logs and monitor errors.

Performance tips

  • Aggregate on the server for charts and reports.

  • Use caching for reference data (categories) using Redis or in-memory cache.

  • Use database indices on Date and UserId.

  • Paginate and limit results.

Testing and monitoring

  • Unit test services with in-memory DB (EF Core InMemory) or SQLite.

  • Integration tests using WebApplicationFactory.

  • E2E tests for Angular using Cypress.

  • Monitor API latency and error rates with Application Insights or Prometheus/Grafana.

Deployment checklist

  • Configure connection strings and secret keys via environment variables or key vault.

  • Configure auto migrations or run migrations at deploy time.

  • Provision storage for receipts and exports.

  • Configure CI/CD: build backend (dotnet publish), build Angular (ng build --prod), and deploy static files to CDN or serve via backend.

Sample sequence diagram (create expense)

User -> Angular UI -> POST /api/expenses -> API validates -> Save DB -> Return 201 -> UI refresh list -> Dashboard update

Final notes and next steps

This article shows a full, production-focused plan to build an Expense Tracker with ASP.NET Core and Angular. Next improvements:

  • Multi-currency support with exchange rates

  • Advanced budgeting rules and alerts

  • Machine learning for expense categorization

  • Mobile apps with same API

If you want, I can now:

  • generate a ready-to-run GitHub scaffold for backend and frontend,

  • add EF Core migrations, seed data and sample SQL,

  • produce Angular components and unit tests,

  • add PNG/SVG architecture and sequence diagrams.

Tell me which one you want next.