← All projects

Banking Application

My first full-stack app: a mock banking system with Angular, ASP.NET Core, EF Core, MSSQL, and Azure AD B2C.


4 roles

customer, teller, officer, manager

The app was designed around different banking jobs instead of one generic user dashboard.

.NET 5

API and domain layer

ASP.NET Core controllers expose role-specific endpoints backed by repository classes and AutoMapper models.

MSSQL

transaction storage

EF Core migrations model the tables, while SQL Server owns balance updates through a transaction trigger.

This was my first serious full-stack application. I built it to understand how a banking app fits together: customers, accounts, payees, transactions, roles, authentication, database relationships, and the path from a deposit or withdrawal to an updated balance.

It is a mock banking app with an Angular frontend and an ASP.NET Core backend. It is not production banking software, but it was the first project where I had to think across the whole stack: browser guards, API controllers, EF Core models, SQL Server schema design, Azure deployment, and how money movement should be stored as data.

What I built

The application is split into four role-based areas:

  • Customer: view personal details, accounts, payees, transaction history, reports, and fund-transfer screens.
  • Teller: create deposit and withdrawal transactions against customer accounts.
  • Officer: create customer accounts, view accounts, edit customer profiles, and inspect transactions.
  • Manager: view customers and manage users.

That role split shaped the project. The frontend has Angular modules for customer, teller, officer, and manager. The backend mirrors that with controller folders and repository interfaces for each role. It made the app easier to follow because each feature had an obvious owner.

flowchart LR
    user["signed-in user"]
    angular["Angular 12 frontend"]
    guards["role guards and routes"]
    api["ASP.NET Core API"]
    repos["role repositories"]
    ef["EF Core DbContext"]
    sql["MSSQL database"]

    user --> angular
    angular --> guards
    guards --> api
    api --> repos
    repos --> ef
    ef --> sql

System structure

Frontend

The UI is an Angular 12 app using Angular Router, reactive forms, Angular Material tables, paginators, sort controls, steppers, date pickers, and MSAL for Azure AD B2C sign-in.

Backend

The API is a .NET 5 ASP.NET Core service. Controllers handle HTTP boundaries, repositories handle EF Core queries, and AutoMapper maps between API models and data entities.

Data model

The data project contains EF Core entities for customers, accounts, addresses, notifications, payees, and transactions, plus migrations and SQL scripts for the database.

Infrastructure

The original deployment used Azure-facing pieces: Azure AD B2C for identity, an Azure-hosted API endpoint, and SQL Server as the backing store.

The backend is split into API and data projects:

BankingApplication-be
├── Bank.API/
│   ├── Controllers/
│   │   ├── Customer/
│   │   ├── Teller/
│   │   ├── Officer/
│   │   └── Manager/
│   ├── Models/
│   ├── Startup.cs
│   └── Program.cs
└── Bank.Data/
    ├── Entities/
    ├── Migrations/
    ├── SQL/
    └── BankContext.cs

The frontend follows the same shape:

BankingApplication-fe
└── src/app/
    ├── customer/
    ├── teller/
    ├── officer/
    ├── manager/
    ├── guards/
    └── shared/

That symmetry helped while I was learning. I could trace a feature from Angular route to controller, repository, and database entity.

Learning banking through data

The most useful lesson was that banking workflows are data workflows. A transfer screen is only the visible part of a deeper model:

  • A customer can own multiple accounts.
  • An account has a balance, status, sort code, opening date, and closing date.
  • A payee belongs to a customer and stores account details for a recipient.
  • A transaction belongs to an account and records amount, type, timestamp, description, and creator.
  • Customer-facing views should read transaction history without being allowed to mutate everything.
  • Teller and officer flows need different permissions and different API endpoints.

The EF Core entities made those ideas concrete. Customer owns accounts, payees, addresses, and notifications. Account owns transactions. Transaction carries a signed decimal amount, so credits and debits can update the balance consistently.

erDiagram
    CUSTOMER ||--o{ ACCOUNT : owns
    CUSTOMER ||--o{ PAYEE : registers
    CUSTOMER ||--o{ ADDRESS : has
    CUSTOMER ||--o{ NOTIFICATION : configures
    ACCOUNT ||--o{ TRANSACTION : records

    CUSTOMER {
        int CustomerId
        string Email
        string Status
        int Budget
    }

    ACCOUNT {
        int AccountId
        string AccountNumber
        string Sortcode
        decimal Balance
        string Status
    }

    TRANSACTION {
        int TransactionId
        string Type
        decimal Amount
        datetime TransDateTime
    }

Transactions and balances

The backend stores transactions in SQL Server through EF Core. For teller deposits and withdrawals, the controller maps the incoming request into a Transaction entity and saves it through the repository. SQL Server then updates the account balance with an AFTER INSERT trigger on the Transactions table.

That design taught me a lesson. Putting the balance update in the database keeps the rule close to the data and guarantees it runs when a transaction row is inserted. It also hides important behavior from the application code. If I rebuilt this today, I would make that flow explicit in an application service and wrap the insert and balance update in a clear database transaction.

sequenceDiagram
    participant Teller as Teller screen
    participant API as Teller transaction API
    participant Repo as TellerRepository
    participant EF as EF Core
    participant DB as SQL Server
    participant Trigger as Transactions trigger

    Teller->>API: POST transaction
    API->>Repo: Add(Transaction)
    Repo->>EF: SaveChangesAsync()
    EF->>DB: INSERT Transactions
    DB->>Trigger: AFTER INSERT
    Trigger->>DB: UPDATE Accounts SET Balance = Balance + Amount
    DB-->>API: persisted transaction
    API-->>Teller: created transaction

The project also made me think about money types. The backend uses decimal(18,2) for balances and transaction amounts, which is the right direction for financial values. It avoids floating-point rounding issues and makes the precision visible in C# and SQL.

NuGet and backend libraries

This was also where I got properly familiar with the .NET ecosystem.

PackageWhy it mattered
Microsoft.EntityFrameworkCore.SqlServerConnected the EF Core model to SQL Server and let the app query real relational data.
Microsoft.EntityFrameworkCore.ToolsGave me migrations and schema evolution instead of hand-editing every database change.
Microsoft.AspNetCore.Mvc.NewtonsoftJsonHelped serialize API responses while avoiding reference-loop issues from EF relationships.
AutoMapperSeparated API models from database entities so controllers did not have to expose persistence classes directly.
Microsoft.Identity.WebIntroduced the ASP.NET side of Microsoft identity and Azure AD B2C protected APIs.
Microsoft.Azure.Services.AppAuthenticationConnected the app to Azure authentication patterns for cloud-hosted database access.

Using ASP.NET Core taught me the shape of a web API: dependency injection, controllers, CORS, route attributes, configuration, logging, and async database calls. Before this project, “backend” felt like one thing. After it, I could separate transport, workflow, persistence, authentication, and deployment.

Frontend experience

The Angular app is where the system became usable. I used Angular Router to divide the app by role, route guards to protect areas, reactive forms for structured input, Angular Material for tables and controls, and MSAL Angular for Azure AD B2C sign-in.

The customer flow was the broadest:

  • view accounts and personal details
  • register and edit payees
  • open transaction history tables
  • filter and sort transactions
  • inspect account reports and charts
  • start a fund transfer flow

The reporting area used ng2-charts and Chart.js. That was my first taste of turning account data into a user-facing summary instead of leaving everything as raw rows.

flowchart TB
    home["home"]
    auth["Azure AD B2C / MSAL"]
    customer["customer area"]
    teller["teller area"]
    officer["officer area"]
    manager["manager area"]

    home --> auth
    auth --> customer
    auth --> teller
    auth --> officer
    auth --> manager

    customer --> payees["payees"]
    customer --> history["transaction history"]
    customer --> reports["reports and charts"]
    customer --> profile["personal details"]

    teller --> deposit["deposit"]
    teller --> withdraw["withdraw"]
    officer --> accounts["create/view accounts"]
    manager --> users["manage users"]

What I would change now

This project is valuable because it shows where I started. Looking back, I would change several things:

  • Move the transaction flow into an explicit application service instead of relying on a database trigger alone.
  • Put API URLs and identity settings behind environment configuration in the Angular app.
  • Re-enable and complete API authentication/authorization on the backend rather than leaving parts commented during development.
  • Add stronger validation around transaction amounts, account status, account ownership, and negative balances.
  • Add integration tests around the deposit/withdrawal path because balance changes are the riskiest behavior.
  • Treat repository interfaces as boundaries only where they carry real value, not as a pattern repeated everywhere.

Those are not reasons to hide the project. They are why it belongs here. It captures the point where I moved from building screens to thinking about systems.

What I learned

The biggest lesson was that full-stack work is coordination. One customer action crosses authentication, routing, forms, HTTP, controllers, mapping, database queries, relationships, and persistence rules. If one layer makes a different assumption, the feature breaks.

I also learned that banking software is less about fancy UI and more about trust in state changes. The transaction history must match the account balance. Role boundaries must make sense. Money needs decimal precision. Database relationships need to reflect the domain. Audit fields like CreatedBy, CreatedDate, ModifiedBy, and ModifiedDate help explain who changed what and when.

For a first full-stack app, Banking Application forced me to learn the uncomfortable parts: backend structure, SQL schema design, authentication, deployment, and the gap between a working demo and a system with clearer boundaries. That is why I still like the project. It was the first one that made the whole stack feel real.