๐Ÿฆ Full Stack Tutorial

ASP.NET Core API + C# + SQL Server + HTML

Build a complete bank/government data system from scratch โ€” database โ†’ backend โ†’ API โ†’ frontend.

ASP.NET Core 8 C# 12 SQL Server / LocalDB Entity Framework Core HTML + JS Swagger / OpenAPI
๐Ÿ—๏ธ Architecture Overview
How all four layers connect โ€” from SQL Server to the browser
REQUEST FLOW
๐ŸŒ HTML / JS
Browser (fetch/AJAX)
โ†’ HTTP โ†’
โšก ASP.NET Core API
Controllers / Routes
โ†’ calls โ†’
๐Ÿ”ท C# Services
Business Logic
โ†’ EF Core โ†’
๐Ÿ—„๏ธ SQL Server
LocalDB / Express
Browser shows data
โ† JSON โ†
Serialises response
โ† returns โ†
Returns models
โ† queries โ†
Rows โ†’ objects
01
What we are building โ€” Bank + Government Data Portal
OVERVIEW

We will build a Financial Records API โ€” the same pattern used by banks and government agencies to expose data securely. The system will have:

  • SQL Server LocalDB โ€” stores accounts, transactions, citizens
  • Entity Framework Core โ€” C# ORM that maps DB tables to C# classes
  • C# Service Layer โ€” business logic (balance checks, filters)
  • ASP.NET Core Web API โ€” REST endpoints at /api/accounts
  • HTML + Fetch โ€” clean frontend that calls the API
โ„น๏ธ
Everything runs locally. No cloud needed. SQL Server LocalDB is free with Visual Studio. Your API runs on https://localhost:7000.
02
Prerequisites โ€” install these first
SETUP
  • .NET 8 SDK โ€” dotnet.microsoft.com
  • Visual Studio 2022 (Community is free) โ€” includes SQL LocalDB
  • SQL Server Management Studio (SSMS) or Azure Data Studio โ€” to view DB
  • Any browser โ€” for the HTML frontend
bash โ€” verify install
dotnet --version          # should print 8.x.x
dotnet new webapi --help  # confirms templates available
03
Project structure we will create
STRUCTURE
project tree
FinanceApi/
โ”œโ”€โ”€ Controllers/
โ”‚   โ””โ”€โ”€ AccountsController.cs     # HTTP endpoints
โ”‚   โ””โ”€โ”€ TransactionsController.cs
โ”œโ”€โ”€ Models/
โ”‚   โ””โ”€โ”€ Account.cs                # C# data classes
โ”‚   โ””โ”€โ”€ Transaction.cs
โ”œโ”€โ”€ Data/
โ”‚   โ””โ”€โ”€ AppDbContext.cs           # EF Core database context
โ”œโ”€โ”€ Services/
โ”‚   โ””โ”€โ”€ AccountService.cs         # business logic
โ”œโ”€โ”€ DTOs/
โ”‚   โ””โ”€โ”€ AccountDto.cs             # data transfer objects
โ”œโ”€โ”€ Program.cs                    # app startup + DI
โ”œโ”€โ”€ appsettings.json              # connection string
โ””โ”€โ”€ frontend/
    โ””โ”€โ”€ index.html                # your browser UI
๐Ÿ—„๏ธ Database โ€” SQL Server LocalDB
Design the tables, then let Entity Framework create them automatically
01
Create the ASP.NET Core project
CLI
bash
# Create Web API project
dotnet new webapi -n FinanceApi --use-controllers
cd FinanceApi

# Install Entity Framework Core + SQL Server provider
dotnet add package Microsoft.EntityFrameworkCore.SqlServer
dotnet add package Microsoft.EntityFrameworkCore.Tools
dotnet add package Microsoft.EntityFrameworkCore.Design

# Install Swagger for API testing
dotnet add package Swashbuckle.AspNetCore
02
Set up the connection string โ€” appsettings.json
CONFIG

This tells EF Core where your SQL Server LocalDB lives. LocalDB creates a .mdf file in your user folder automatically.

appsettings.json
{
  "ConnectionStrings": {
    "DefaultConnection": "Server=(localdb)\\MSSQLLocalDB;Database=FinanceDb;Trusted_Connection=True;MultipleActiveResultSets=true"
  },
  "AllowedHosts": "*"
}
โš ๏ธ
If you installed SQL Server Express instead of LocalDB, use: "Server=.\\SQLEXPRESS;Database=FinanceDb;Trusted_Connection=True"
03
Create the C# Model classes โ€” these become DB tables
MODELS

Each C# class becomes a SQL table. Each property becomes a column. EF Core handles this mapping automatically.

Models/Account.cs
using System.ComponentModel.DataAnnotations;

namespace FinanceApi.Models;

public class Account
{
    public int Id { get; set; }          // PRIMARY KEY (auto-increment)

    [Required, MaxLength(100)]
    public string OwnerName { get; set; } = "";

    [Required, MaxLength(20)]
    public string AccountNumber { get; set; } = "";

    public decimal Balance { get; set; }

    public string AccountType { get; set; } = "Checking"; // Savings, Govt

    public DateTime CreatedAt { get; set; } = DateTime.UtcNow;

    public bool IsActive { get; set; } = true;

    // Navigation property โ€” one account has many transactions
    public ICollection<Transaction> Transactions { get; set; } = [];
}
Models/Transaction.cs
namespace FinanceApi.Models;

public class Transaction
{
    public int Id { get; set; }

    public int AccountId { get; set; }   // FOREIGN KEY
    public Account Account { get; set; } = null!;

    public decimal Amount { get; set; }

    public string Type { get; set; } = "Credit"; // Debit, Transfer

    public string Description { get; set; } = "";

    public DateTime Date { get; set; } = DateTime.UtcNow;

    public string Status { get; set; } = "Completed"; // Pending, Failed
}
04
Create AppDbContext โ€” the bridge between C# and SQL
EF CORE

The DbContext is the heart of EF Core. It maps your C# classes to SQL tables and handles all queries.

Data/AppDbContext.cs
using Microsoft.EntityFrameworkCore;
using FinanceApi.Models;

namespace FinanceApi.Data;

public class AppDbContext : DbContext
{
    public AppDbContext(DbContextOptions<AppDbContext> options)
        : base(options) { }

    // These become SQL tables automatically
    public DbSet<Account> Accounts => Set<Account>();
    public DbSet<Transaction> Transactions => Set<Transaction>();

    protected override void OnModelCreating(ModelBuilder mb)
    {
        // Precision for money โ€” important for banking!
        mb.Entity<Account>()
          .Property(a => a.Balance)
          .HasPrecision(18, 2);

        mb.Entity<Transaction>()
          .Property(t => t.Amount)
          .HasPrecision(18, 2);

        // Seed data โ€” so DB is not empty on first run
        mb.Entity<Account>().HasData(
            new Account { Id=1, OwnerName="Ministry of Finance",
                AccountNumber="GOV-001", Balance=9_500_000, AccountType="Government" },
            new Account { Id=2, OwnerName="Central Bank Reserve",
                AccountNumber="CB-001", Balance=250_000_000, AccountType="Reserve" },
            new Account { Id=3, OwnerName="John Citizen",
                AccountNumber="ACC-8821", Balance=4_200.50m, AccountType="Checking" }
        );
    }
}
05
Run migrations โ€” create the actual SQL tables
MIGRATIONS

EF Core will read your C# models and generate SQL CREATE TABLE statements automatically. You never write raw DDL.

bash โ€” in project root
# Generate migration files (C# code that describes DB changes)
dotnet ef migrations add InitialCreate

# Apply the migration โ€” this creates the SQL tables in LocalDB
dotnet ef database update

# You should see: "Done." in terminal
# Tables created: Accounts, Transactions, __EFMigrationsHistory
โœ…
Open SSMS and connect to (localdb)\MSSQLLocalDB โ€” you will see your FinanceDb database with the tables already created and seed data inserted.
๐Ÿ”ท C# Service Layer โ€” Business Logic
The service layer sits between the API controllers and the database. Controllers call services, services call the DB.
01
Create DTOs โ€” what you send to the browser
DTO

DTOs (Data Transfer Objects) are what you expose via the API. Never expose your DB model directly โ€” DTOs let you control exactly what data the browser sees.

DTOs/AccountDto.cs
namespace FinanceApi.DTOs;

// What the API returns to the browser
public record AccountDto(
    int Id,
    string OwnerName,
    string AccountNumber,
    decimal Balance,
    string AccountType,
    bool IsActive,
    DateTime CreatedAt
);

// What the browser sends to CREATE an account
public record CreateAccountDto(
    string OwnerName,
    string AccountNumber,
    decimal InitialBalance,
    string AccountType
);

// Transaction DTO
public record TransactionDto(
    int Id,
    int AccountId,
    decimal Amount,
    string Type,
    string Description,
    DateTime Date,
    string Status
);
02
Create AccountService โ€” the business logic layer
SERVICE

The service is a plain C# class. It uses Entity Framework Core to query SQL Server using LINQ โ€” no raw SQL needed.

Services/AccountService.cs
using Microsoft.EntityFrameworkCore;
using FinanceApi.Data;
using FinanceApi.DTOs;
using FinanceApi.Models;

namespace FinanceApi.Services;

public interface IAccountService
{
    Task<List<AccountDto>> GetAllAsync(string? type = null);
    Task<AccountDto?> GetByIdAsync(int id);
    Task<AccountDto> CreateAsync(CreateAccountDto dto);
    Task<bool> DeleteAsync(int id);
    Task<List<TransactionDto>> GetTransactionsAsync(int accountId);
}

public class AccountService : IAccountService
{
    private readonly AppDbContext _db;

    public AccountService(AppDbContext db) => _db = db;

    // GET all accounts, optionally filtered by type
    public async Task<List<AccountDto>> GetAllAsync(string? type = null)
    {
        var query = _db.Accounts.AsQueryable();

        if (!string.IsNullOrEmpty(type))
            query = query.Where(a => a.AccountType == type);

        return await query
            .Select(a => new AccountDto(
                a.Id, a.OwnerName, a.AccountNumber,
                a.Balance, a.AccountType, a.IsActive, a.CreatedAt))
            .ToListAsync();
    }

    // GET single account by ID
    public async Task<AccountDto?> GetByIdAsync(int id)
    {
        var a = await _db.Accounts.FindAsync(id);
        if (a is null) return null;
        return new AccountDto(a.Id, a.OwnerName, a.AccountNumber,
            a.Balance, a.AccountType, a.IsActive, a.CreatedAt);
    }

    // POST โ€” create new account
    public async Task<AccountDto> CreateAsync(CreateAccountDto dto)
    {
        var account = new Account
        {
            OwnerName     = dto.OwnerName,
            AccountNumber = dto.AccountNumber,
            Balance       = dto.InitialBalance,
            AccountType   = dto.AccountType
        };
        _db.Accounts.Add(account);
        await _db.SaveChangesAsync();  // writes to SQL Server

        return new AccountDto(account.Id, account.OwnerName,
            account.AccountNumber, account.Balance,
            account.AccountType, account.IsActive, account.CreatedAt);
    }

    // DELETE account
    public async Task<bool> DeleteAsync(int id)
    {
        var account = await _db.Accounts.FindAsync(id);
        if (account is null) return false;
        _db.Accounts.Remove(account);
        await _db.SaveChangesAsync();
        return true;
    }

    // GET transactions for an account
    public async Task<List<TransactionDto>> GetTransactionsAsync(int accountId)
    {
        return await _db.Transactions
            .Where(t => t.AccountId == accountId)
            .OrderByDescending(t => t.Date)
            .Select(t => new TransactionDto(
                t.Id, t.AccountId, t.Amount,
                t.Type, t.Description, t.Date, t.Status))
            .ToListAsync();
    }
}
โšก ASP.NET Core Web API โ€” Controllers + Startup
The API layer exposes HTTP routes. Program.cs wires everything together with Dependency Injection.
01
Program.cs โ€” startup, DI, CORS, Swagger
STARTUP

This is the entry point. Register your services, set up CORS (so your HTML page can call the API), add Swagger for testing.

Program.cs
using Microsoft.EntityFrameworkCore;
using FinanceApi.Data;
using FinanceApi.Services;

var builder = WebApplication.CreateBuilder(args);

// โ”€โ”€ Database โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
builder.Services.AddDbContext<AppDbContext>(opt =>
    opt.UseSqlServer(builder.Configuration
        .GetConnectionString("DefaultConnection")));

// โ”€โ”€ Dependency Injection โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
builder.Services.AddScoped<IAccountService, AccountService>();

// โ”€โ”€ CORS โ€” allow HTML file to call this API โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
builder.Services.AddCors(options =>
{
    options.AddPolicy("AllowAll", policy =>
        policy.AllowAnyOrigin()
              .AllowAnyHeader()
              .AllowAnyMethod());
});

builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen(c =>
{
    c.SwaggerDoc("v1", new { Title = "Finance API", Version = "v1" });
});

var app = builder.Build();

// โ”€โ”€ Auto-apply migrations on startup โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
using (var scope = app.Services.CreateScope())
{
    var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
    db.Database.Migrate();  // creates DB if it doesn't exist
}

// โ”€โ”€ Middleware pipeline โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();  // visit /swagger to test API
}

app.UseCors("AllowAll");   // MUST be before MapControllers
app.UseHttpsRedirection();
app.MapControllers();

app.Run();
02
AccountsController โ€” the HTTP endpoints
CONTROLLER

Controllers define your REST routes. Each method maps to a HTTP verb (GET, POST, PUT, DELETE). The controller calls the service โ€” it does NO business logic itself.

Controllers/AccountsController.cs
using Microsoft.AspNetCore.Mvc;
using FinanceApi.DTOs;
using FinanceApi.Services;

namespace FinanceApi.Controllers;

[ApiController]
[Route("api/[controller]")]   // โ†’ /api/accounts
public class AccountsController : ControllerBase
{
    private readonly IAccountService _svc;

    public AccountsController(IAccountService svc) => _svc = svc;

    // GET /api/accounts           โ€” all accounts
    // GET /api/accounts?type=Government โ€” filtered
    [HttpGet]
    public async Task<IActionResult> GetAll([FromQuery] string? type)
    {
        var accounts = await _svc.GetAllAsync(type);
        return Ok(accounts);   // 200 + JSON array
    }

    // GET /api/accounts/3  โ€” single account
    [HttpGet("{id}")]
    public async Task<IActionResult> GetById(int id)
    {
        var account = await _svc.GetByIdAsync(id);
        return account is null ? NotFound() : Ok(account);
    }

    // POST /api/accounts  โ€” create account
    [HttpPost]
    public async Task<IActionResult> Create([FromBody] CreateAccountDto dto)
    {
        if (!ModelState.IsValid) return BadRequest(ModelState);
        var created = await _svc.CreateAsync(dto);
        return CreatedAtAction(nameof(GetById),
            new { id = created.Id }, created);   // 201 Created
    }

    // DELETE /api/accounts/3
    [HttpDelete("{id}")]
    public async Task<IActionResult> Delete(int id)
    {
        var deleted = await _svc.DeleteAsync(id);
        return deleted ? NoContent() : NotFound();  // 204 or 404
    }

    // GET /api/accounts/3/transactions
    [HttpGet("{id}/transactions")]
    public async Task<IActionResult> GetTransactions(int id)
    {
        var txns = await _svc.GetTransactionsAsync(id);
        return Ok(txns);
    }
}
โ„น๏ธ
After running the API, visit https://localhost:7000/swagger to get an interactive UI to test every endpoint without writing any frontend code yet.
03
Run the API
RUN
bash
dotnet run

# Output:
# Now listening on: https://localhost:7000
# Now listening on: http://localhost:5000

# Test it immediately:
curl https://localhost:7000/api/accounts
# Returns JSON array of accounts from your SQL Server DB!
๐ŸŒ HTML Frontend โ€” Calls the ASP.NET API
A clean, professional frontend that uses the Fetch API to talk to your locally running ASP.NET Core backend
01
The complete frontend โ€” index.html
HTML + JS

Save this as frontend/index.html and open it in your browser. It connects directly to your local ASP.NET API.

โš ๏ธ
Change localhost:7000 to match whatever port your API runs on (check terminal output).
frontend/index.html
<!-- Full bank/government data portal frontend -->
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Finance Portal</title>
  <style>
    * { box-sizing: border-box; margin: 0; padding: 0; }
    body { font-family: 'Segoe UI', sans-serif; background: #f0f4f8; color: #1e293b; }

    header {
      background: linear-gradient(135deg, #1e3a5f, #0f2340);
      color: white; padding: 20px 32px;
      display: flex; align-items: center; gap: 16px;
    }
    header h1 { font-size: 22px; font-weight: 700; }
    header p  { font-size: 13px; opacity: 0.7; margin-top: 2px; }
    .status-dot { width: 10px; height: 10px; border-radius: 50%;
      background: #10b981; margin-left: auto; animation: pulse 2s infinite; }
    @keyframes pulse { 0%,100%{opacity:1} 50%{opacity:0.4} }

    .toolbar {
      background: white; border-bottom: 1px solid #e2e8f0;
      padding: 12px 32px; display: flex; gap: 8px; flex-wrap: wrap;
    }
    button {
      padding: 8px 16px; border: 1px solid #cbd5e1; border-radius: 6px;
      cursor: pointer; font-size: 13px; font-weight: 500; background: white;
      transition: all 0.15s;
    }
    button:hover { background: #1e3a5f; color: white; border-color: #1e3a5f; }
    button.primary { background: #1e3a5f; color: white; border-color: #1e3a5f; }
    button.danger  { background: #dc2626; color: white; border-color: #dc2626; }
    input, select {
      padding: 8px 12px; border: 1px solid #cbd5e1; border-radius: 6px;
      font-size: 13px; min-width: 160px;
    }

    main { padding: 24px 32px; }
    #status { margin-bottom: 16px; font-size: 13px; color: #64748b; }
    #status.error { color: #dc2626; }
    #status.success { color: #059669; }

    .grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); gap: 16px; }
    .card {
      background: white; border-radius: 10px; padding: 20px;
      border: 1px solid #e2e8f0; transition: box-shadow 0.2s;
    }
    .card:hover { box-shadow: 0 4px 16px rgba(0,0,0,0.08); }
    .card-header { display: flex; align-items: center; gap: 12px; margin-bottom: 14px; }
    .avatar {
      width: 42px; height: 42px; border-radius: 8px; display: flex;
      align-items: center; justify-content: center; font-size: 20px; flex-shrink: 0;
    }
    .av-gov  { background: #dbeafe; }
    .av-bank { background: #fef3c7; }
    .av-user { background: #d1fae5; }
    .card h3 { font-size: 15px; font-weight: 600; }
    .card .acn { font-size: 12px; color: #94a3b8; font-family: monospace; }
    .balance { font-size: 22px; font-weight: 700; color: #1e293b; margin: 10px 0 4px; }
    .balance.large { color: #1d4ed8; }
    .badge {
      display: inline-block; padding: 2px 8px; border-radius: 12px; font-size: 11px;
      font-weight: 600; text-transform: uppercase; letter-spacing: 0.5px;
    }
    .badge.govt   { background: #dbeafe; color: #1d4ed8; }
    .badge.bank   { background: #fef3c7; color: #92400e; }
    .badge.check  { background: #d1fae5; color: #065f46; }
    .badge.save   { background: #f3e8ff; color: #6b21a8; }
    .card-footer { display: flex; gap: 8px; margin-top: 14px; }

    .modal-overlay {
      display: none; position: fixed; inset: 0;
      background: rgba(0,0,0,0.5); z-index: 100;
      align-items: center; justify-content: center;
    }
    .modal-overlay.open { display: flex; }
    .modal {
      background: white; border-radius: 12px; padding: 28px;
      width: 420px; max-width: 92vw;
    }
    .modal h2 { font-size: 18px; font-weight: 700; margin-bottom: 20px; }
    .field { margin-bottom: 14px; }
    .field label { display: block; font-size: 12px; font-weight: 600;
      color: #64748b; margin-bottom: 4px; text-transform: uppercase; letter-spacing: 0.5px; }
    .field input, .field select { width: 100%; }
    .modal-actions { display: flex; gap: 8px; justify-content: flex-end; margin-top: 20px; }

    #txn-panel {
      display: none; background: white; border: 1px solid #e2e8f0;
      border-radius: 10px; margin-bottom: 20px; overflow: hidden;
    }
    #txn-panel.open { display: block; }
    #txn-header { background: #1e3a5f; color: white; padding: 14px 20px; font-weight: 600; font-size: 14px; }
    table { width: 100%; border-collapse: collapse; font-size: 13px; }
    th { text-align: left; padding: 10px 16px; background: #f8fafc;
      color: #64748b; font-weight: 600; border-bottom: 1px solid #e2e8f0; font-size: 11px; text-transform: uppercase; }
    td { padding: 10px 16px; border-bottom: 1px solid #f1f5f9; }
    tr:last-child td { border-bottom: none; }
    .credit { color: #059669; font-weight: 600; }
    .debit  { color: #dc2626; font-weight: 600; }
    .loading { text-align: center; padding: 40px; color: #94a3b8; font-size: 14px; }
    .empty  { text-align: center; padding: 60px; color: #94a3b8; }
    .empty .icon { font-size: 48px; margin-bottom: 12px; }
  </style>
</head>
<body>

<header>
  <div>
    <h1>๐Ÿฆ Finance Data Portal</h1>
    <p>Connected to ASP.NET Core API โ€” localhost:7000</p>
  </div>
  <div class="status-dot" id="statusDot" title="API Status"></div>
</header>

<div class="toolbar">
  <button class="primary" onclick="loadAccounts()">๐Ÿ”„ Refresh</button>
  <button onclick="openModal()">โž• New Account</button>
  <select id="typeFilter" onchange="loadAccounts()">
    <option value="">All Types</option>
    <option value="Government">Government</option>
    <option value="Reserve">Reserve</option>
    <option value="Checking">Checking</option>
    <option value="Savings">Savings</option>
  </select>
  <input id="search" placeholder="Search by name..." oninput="filterCards()" />
</div>

<main>
  <div id="status">Loading accounts...</div>
  <div id="txn-panel">
    <div id="txn-header">Transactions</div>
    <table>
      <thead><tr>
        <th>Date</th><th>Description</th>
        <th>Type</th><th>Amount</th><th>Status</th>
      </tr></thead>
      <tbody id="txn-body"></tbody>
    </table>
  </div>
  <div class="grid" id="accountGrid">
    <div class="loading">โณ Connecting to API...</div>
  </div>
</main>

<!-- CREATE ACCOUNT MODAL -->
<div class="modal-overlay" id="modal">
  <div class="modal">
    <h2>โž• Create New Account</h2>
    <div class="field">
      <label>Owner Name</label>
      <input id="f-name" placeholder="e.g. Ministry of Transport" />
    </div>
    <div class="field">
      <label>Account Number</label>
      <input id="f-acn" placeholder="e.g. GOV-002" />
    </div>
    <div class="field">
      <label>Initial Balance ($)</label>
      <input id="f-bal" type="number" placeholder="0.00" />
    </div>
    <div class="field">
      <label>Account Type</label>
      <select id="f-type">
        <option>Government</option>
        <option>Reserve</option>
        <option>Checking</option>
        <option>Savings</option>
      </select>
    </div>
    <div class="modal-actions">
      <button onclick="closeModal()">Cancel</button>
      <button class="primary" onclick="createAccount()">Create Account</button>
    </div>
  </div>
</div>

<script>
  const API = 'https://localhost:7000/api';  // โ† change port if needed
  let allCards = [];

  // โ”€โ”€ LOAD ACCOUNTS โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
  async function loadAccounts() {
    const type = document.getElementById('typeFilter').value;
    const url  = type ? `${API}/accounts?type=${type}` : `${API}/accounts`;
    setStatus('Loading...', '');

    try {
      const res  = await fetch(url);
      if (!res.ok) throw new Error(`API error: ${res.status}`);
      const data = await res.json();
      renderCards(data);
      setStatus(`โœ… ${data.length} accounts loaded`, 'success');
      document.getElementById('statusDot').style.background = '#10b981';
    } catch (err) {
      setStatus(`โŒ Cannot reach API: ${err.message}. Is the API running?`, 'error');
      document.getElementById('statusDot').style.background = '#ef4444';
      document.getElementById('accountGrid').innerHTML =
        `<div class="empty"><div class="icon">โš ๏ธ</div>
         Could not connect to API.<br>Run: <code>dotnet run</code></div>`;
    }
  }

  // โ”€โ”€ RENDER CARDS โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
  function renderCards(accounts) {
    allCards = accounts;
    if (!accounts.length) {
      document.getElementById('accountGrid').innerHTML =
        `<div class="empty"><div class="icon">๐Ÿฆ</div>No accounts found.</div>`;
      return;
    }
    document.getElementById('accountGrid').innerHTML = accounts.map(a => {
      const icons = { Government:'๐Ÿ›๏ธ', Reserve:'๐Ÿฆ', Checking:'๐Ÿ’ณ', Savings:'๐Ÿช™' };
      const avClass = { Government:'av-gov', Reserve:'av-bank',
                        Checking:'av-user', Savings:'av-user' };
      const badgeClass = { Government:'govt', Reserve:'bank',
                           Checking:'check', Savings:'save' };
      const big = a.balance > 1_000_000;
      return `
      <div class="card" id="card-${a.id}">
        <div class="card-header">
          <div class="avatar ${avClass[a.accountType]||'av-user'}">
            ${icons[a.accountType]||'๐Ÿ‘ค'}
          </div>
          <div>
            <div class="card-h3"><strong>${a.ownerName}</strong></div>
            <div class="acn">${a.accountNumber}</div>
          </div>
        </div>
        <div class="balance ${big?'large':''}">
          $${a.balance.toLocaleString('en-US', {minimumFractionDigits:2})}
        </div>
        <span class="badge ${badgeClass[a.accountType]||'check'}">
          ${a.accountType}
        </span>
        <span style="font-size:11px;color:#94a3b8;margin-left:8px;">
          ${a.isActive ? 'โœ… Active' : 'โŒ Inactive'}
        </span>
        <div class="card-footer">
          <button onclick="viewTransactions(${a.id}, '${a.ownerName}')">
            ๐Ÿ“‹ Transactions
          </button>
          <button class="danger" onclick="deleteAccount(${a.id})">๐Ÿ—‘</button>
        </div>
      </div>`;
    }).join('');
  }

  // โ”€โ”€ SEARCH FILTER โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
  function filterCards() {
    const q = document.getElementById('search').value.toLowerCase();
    const filtered = allCards.filter(a =>
      a.ownerName.toLowerCase().includes(q) ||
      a.accountNumber.toLowerCase().includes(q));
    renderCards(filtered);
  }

  // โ”€โ”€ VIEW TRANSACTIONS โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
  async function viewTransactions(id, name) {
    const panel = document.getElementById('txn-panel');
    document.getElementById('txn-header').textContent = `Transactions โ€” ${name}`;
    document.getElementById('txn-body').innerHTML = '<tr><td colspan="5" class="loading">Loading...</td></tr>';
    panel.classList.add('open');
    panel.scrollIntoView({ behavior: 'smooth' });

    try {
      const res  = await fetch(`${API}/accounts/${id}/transactions`);
      const txns = await res.json();
      if (!txns.length) {
        document.getElementById('txn-body').innerHTML =
          '<tr><td colspan="5" class="loading">No transactions yet.</td></tr>';
        return;
      }
      document.getElementById('txn-body').innerHTML = txns.map(t => `
        <tr>
          <td>${new Date(t.date).toLocaleDateString()}</td>
          <td>${t.description || 'โ€”'}</td>
          <td>${t.type}</td>
          <td class="${t.type==='Credit'?'credit':'debit'}">
            ${t.type==='Credit'?'+':'-'}$${t.amount.toFixed(2)}
          </td>
          <td>${t.status}</td>
        </tr>`).join('');
    } catch { document.getElementById('txn-body').innerHTML = '<tr><td colspan="5">Error loading.</td></tr>'; }
  }

  // โ”€โ”€ CREATE ACCOUNT โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
  async function createAccount() {
    const body = {
      ownerName:      document.getElementById('f-name').value,
      accountNumber:  document.getElementById('f-acn').value,
      initialBalance: parseFloat(document.getElementById('f-bal').value) || 0,
      accountType:    document.getElementById('f-type').value
    };
    if (!body.ownerName || !body.accountNumber) {
      alert('Name and account number are required.'); return;
    }
    try {
      const res = await fetch(`${API}/accounts`, {
        method:  'POST',
        headers: { 'Content-Type': 'application/json' },
        body:    JSON.stringify(body)
      });
      if (!res.ok) throw new Error(await res.text());
      closeModal();
      await loadAccounts();
      setStatus('โœ… Account created successfully!', 'success');
    } catch (err) { alert('Error: ' + err.message); }
  }

  // โ”€โ”€ DELETE ACCOUNT โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
  async function deleteAccount(id) {
    if (!confirm('Delete this account?')) return;
    try {
      const res = await fetch(`${API}/accounts/${id}`, { method: 'DELETE' });
      if (res.ok) { await loadAccounts(); setStatus('๐Ÿ—‘๏ธ Account deleted.', 'success'); }
      else setStatus('Could not delete.', 'error');
    } catch { setStatus('API error.', 'error'); }
  }

  // โ”€โ”€ HELPERS โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
  function setStatus(msg, cls) {
    const el = document.getElementById('status');
    el.textContent = msg; el.className = cls;
  }
  function openModal()  { document.getElementById('modal').classList.add('open'); }
  function closeModal() { document.getElementById('modal').classList.remove('open'); }
  document.getElementById('modal').onclick = e =>
    { if (e.target === e.currentTarget) closeModal(); };

  loadAccounts();
</script>
</body>
</html>
๐Ÿ”Œ Wire It All Up โ€” Run Order
Step-by-step: start the API, check the DB, open the frontend
01
Complete run sequence
RUN
bash โ€” full flow
# 1. Create the project
dotnet new webapi -n FinanceApi --use-controllers
cd FinanceApi

# 2. Install packages
dotnet add package Microsoft.EntityFrameworkCore.SqlServer
dotnet add package Microsoft.EntityFrameworkCore.Tools
dotnet add package Microsoft.EntityFrameworkCore.Design
dotnet add package Swashbuckle.AspNetCore

# 3. Add all the files (Models, Data, Services, Controllers, DTOs)
#    โ€” copy all code from tabs 2, 3, 4

# 4. Run migrations (creates SQL Server DB + tables)
dotnet ef migrations add InitialCreate
dotnet ef database update

# 5. Run the API
dotnet run

# 6. Open Swagger to test API
#    โ†’ https://localhost:7000/swagger

# 7. Open frontend/index.html in browser
#    โ†’ Double-click the file or use Live Server extension in VS Code
02
What each layer actually does โ€” explained
DEEP DIVE

Here is what happens when you click "Refresh" in the browser:

  1. Browser calls fetch("https://localhost:7000/api/accounts")
  2. ASP.NET Core receives the HTTP GET request on port 7000
  3. Router matches route /api/accounts โ†’ AccountsController.GetAll()
  4. Controller calls _svc.GetAllAsync() โ€” the service layer
  5. Service runs _db.Accounts.Select(...).ToListAsync()
  6. Entity Framework translates that LINQ query to SELECT * FROM Accounts
  7. SQL Server LocalDB executes the SQL and returns rows
  8. EF Core maps rows โ†’ C# objects โ†’ DTOs
  9. Controller returns Ok(accounts) โ†’ HTTP 200 with JSON body
  10. Browser receives JSON, calls renderCards(data), cards appear
03
Common errors and fixes
TROUBLESHOOT
โš ๏ธ
CORS error in browser: Make sure app.UseCors("AllowAll") is in Program.cs BEFORE app.MapControllers(). Order matters.
โš ๏ธ
SSL certificate error (fetch fails): Run dotnet dev-certs https --trust in terminal, then restart browser.
โš ๏ธ
Cannot connect to LocalDB: Run SqlLocalDB start in terminal, or open Visual Studio once (it starts LocalDB automatically).
โœ…
Use http instead of https for the frontend if cert issues: change the API const to http://localhost:5000/api and use the HTTP port shown in dotnet run output.
04
Next steps โ€” make it production-grade
NEXT
  • Authentication โ€” add Microsoft.AspNetCore.Authentication.JwtBearer to require JWT tokens (how banks actually secure APIs)
  • Pagination โ€” add ?page=1&size=20 query params to avoid loading 10,000 rows
  • Validation โ€” add FluentValidation package for complex business rules
  • Logging โ€” use ILogger<T> injected into services (already built into ASP.NET)
  • Unit Tests โ€” add xUnit + mock AppDbContext with in-memory provider
  • React/Angular frontend โ€” replace the HTML with a proper SPA using the same API
  • Deploy โ€” publish to Azure App Service with dotnet publish + Azure SQL for the DB