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.
- Frontend repository: BankingApplication-fe
- Backend repository: BankingApplication-be
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.
| Package | Why it mattered |
|---|---|
| Microsoft.EntityFrameworkCore.SqlServer | Connected the EF Core model to SQL Server and let the app query real relational data. |
| Microsoft.EntityFrameworkCore.Tools | Gave me migrations and schema evolution instead of hand-editing every database change. |
| Microsoft.AspNetCore.Mvc.NewtonsoftJson | Helped serialize API responses while avoiding reference-loop issues from EF relationships. |
| AutoMapper | Separated API models from database entities so controllers did not have to expose persistence classes directly. |
| Microsoft.Identity.Web | Introduced the ASP.NET side of Microsoft identity and Azure AD B2C protected APIs. |
| Microsoft.Azure.Services.AppAuthentication | Connected 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.