In the ever-evolving world of web development, both ASP.NET Core and Angular have come a long way since their earlier versions. This updated guide brings a fresh take on the classic full-stack approach by combining ASP.NET Core 8.0 (with minimal APIs and Entity Framework Core) and Angular 20 (using standalone components and signals) to build a modern CRUD web application with SQL Server as the backend database.
In this step-by-step tutorial, you’ll learn how to:
-
Build a RESTful Web API using ASP.NET Core 8.0 and Entity Framework Core.
-
Create a responsive Angular 20 frontend using HttpClient to interact with the API.
-
Perform full CRUD operations (Create, Read, Update, Delete).
-
Connect the Angular frontend to the ASP.NET backend.
-
Run and test your full-stack application locally.
Whether you're modernizing an existing app or starting from scratch, this guide will give you a clean, up-to-date foundation.
Prerequisites
Make sure the following tools are installed on your machine before you begin:
🔧 Backend Requirements (ASP.NET Core 8.0)
-
SQL Server (LocalDB, Developer, or Express Edition). We are using Docker version.
docker pull mcr.microsoft.com/mssql/server:2022-latest docker run -e "ACCEPT_EULA=Y" -e "SA_PASSWORD=YourStrongPassword1" -p 1433:1433 --name sqlserver -d mcr.microsoft.com/mssql/server:2022-latest
-
IDE: Visual Studio 2022+ (with ASP.NET & web development workload) or Visual Studio Code
💻 Frontend Requirements (Angular 20)
-
Angular CLI v20:
npm install -g @angular/cli
-
Code Editor: VS Code is recommended
📦 General
-
Basic familiarity with C#, TypeScript, and Angular
-
Command line (Terminal, Command Prompt, or PowerShell)
Set Up ASP.NET Core 8.0 Web API
We’ll start by creating a RESTful API using ASP.NET Core 8.0 with Entity Framework Core and SQL Server for data storage.
1. 🛠 Create a New ASP.NET Core Web API Project
Open a terminal or use Visual Studio/VS Code and run:
dotnet new webapi -n ProductApi
cd ProductApi
This scaffolds a minimal API template with weather forecast samples, which we’ll replace with a custom Product API.
2. 🧱 Install EF Core SQL Server Provider
Inside the ProductApi
folder, install Entity Framework Core and the SQL Server provider:
dotnet add package Microsoft.EntityFrameworkCore.SqlServer
dotnet add package Microsoft.EntityFrameworkCore.Tools
3. 📦 Create the Product Model
Create a new folder called Models
and add Product.cs
:
using Microsoft.EntityFrameworkCore;
namespace ProductApi.Models;
public class Product
{
public int Id { get; set; }
public string Name { get; set; } = string.Empty;
public string Description { get; set; } = string.Empty;
[Precision(18, 2)]
public decimal Price { get; set; }
}
4. 🧩 Create the EF Core DbContext
Create a Data
folder and add AppDbContext.cs
:
using Microsoft.EntityFrameworkCore;
using ProductApi.Models;
namespace ProductApi.Data;
public class AppDbContext(DbContextOptions<AppDbContext> options) : DbContext(options)
{
public DbSet<Product> Products => Set<Product>();
}
5. 🔧 Configure the Database Connection
Open appsettings.json
and add your SQL Server connection string:
"ConnectionStrings": {
"DefaultConnection": "Server=(localdb)\\MSSQLLocalDB;Database=ProductDb;Trusted_Connection=True;"
}
Then open Program.cs
and update it:
using Microsoft.EntityFrameworkCore;
using ProductApi.Data;
var builder = WebApplication.CreateBuilder(args);
// Add services
builder.Services.AddDbContext<AppDbContext>(options =>
options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection")));
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
var app = builder.Build();
// Enable Swagger
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
// Minimal CRUD endpoints will go here
app.UseHttpsRedirection();
app.Run();
Implement CRUD API Endpoints (Minimal APIs)
We’ll now define routes to Create, Read, Update, and Delete Product
records.
1. ✏️ Add Minimal API Endpoints in Program.cs
At the bottom of Program.cs
, after app.UseHttpsRedirection();
, insert the following code:
using ProductApi.Models;
using ProductApi.Data;
app.MapGet("/api/products", async (AppDbContext db) =>
await db.Products.ToListAsync());
app.MapGet("/api/products/{id:int}", async (int id, AppDbContext db) =>
await db.Products.FindAsync(id) is Product product
? Results.Ok(product)
: Results.NotFound());
app.MapPost("/api/products", async (Product product, AppDbContext db) =>
{
db.Products.Add(product);
await db.SaveChangesAsync();
return Results.Created($"/api/products/{product.Id}", product);
});
app.MapPut("/api/products/{id:int}", async (int id, Product updatedProduct, AppDbContext db) =>
{
var product = await db.Products.FindAsync(id);
if (product is null) return Results.NotFound();
product.Name = updatedProduct.Name;
product.Description = updatedProduct.Description;
product.Price = updatedProduct.Price;
await db.SaveChangesAsync();
return Results.NoContent();
});
app.MapDelete("/api/products/{id:int}", async (int id, AppDbContext db) =>
{
var product = await db.Products.FindAsync(id);
if (product is null) return Results.NotFound();
db.Products.Remove(product);
await db.SaveChangesAsync();
return Results.NoContent();
});
Apply EF Core Migrations and Create the Database
Run the following commands in your terminal:
dotnet ef migrations add InitialCreate
dotnet ef database update
If you're using Visual Studio, you can also run these from the Package Manager Console.
This will generate the Migrations
folder and create the ProductDb
database in SQL Server.
Test the API
1. Run the app:
dotnet run
2. Open your browser and navigate to:
http://localhost:5104/swagger
You’ll see a full Swagger UI with your GET
, POST
, PUT
, and DELETE
routes for /api/products
.
Enable CORS for Angular 20
To allow your Angular frontend (usually served on http://localhost:4200
) to make API requests to your backend (https://localhost:5001
), you need to enable CORS in your ASP.NET Core app.
1. In Program.cs
, register the CORS service:
builder.Services.AddCors(options =>
{
options.AddPolicy("AllowAngularApp", policy =>
{
policy.WithOrigins("http://localhost:4200")
.AllowAnyHeader()
.AllowAnyMethod();
});
});
2. Add the CORS middleware before app.Run()
:
app.UseCors("AllowAngularApp");
Final Program.cs
(essential parts):
builder.Services.AddCors(options =>
{
options.AddPolicy("AllowAngularApp", policy =>
{
policy.WithOrigins("http://localhost:4200")
.AllowAnyHeader()
.AllowAnyMethod();
});
});
...
var app = builder.Build();
...
app.UseCors("AllowAngularApp");
app.Run();
With CORS enabled, your Angular app can now safely consume the API.
Step-by-Step: Create an Angular 20 Frontend for Product CRUD
1. 🧱 Create a New Angular 20 App
In a separate folder:
ng new product-frontend --standalone --routing --style=css
cd product-frontend
Serve the app to confirm:
ng serve
Open your browser to http://localhost:4200
2. 📦 Generate a Product Service
ng generate service services/product.services
Edit src/app/services/product.ts
:
import { HttpClient } from '@angular/common/http';
import { computed, Injectable, signal } from '@angular/core';
import { tap } from 'rxjs';
import { Product } from '../models/product.model';
@Injectable({
providedIn: 'root'
})
export class ProductServices {
private baseUrl = 'https://localhost:5001/api/products';
private _products = signal<Product[]>([]);
products = computed(() => this._products());
constructor(private http: HttpClient) { }
getAll() {
return this.http.get<Product[]>(this.baseUrl).pipe(
tap(data => this._products.set(data))
);
}
getById(id: number) {
return this.http.get<Product>(`${this.baseUrl}/${id}`);
}
create(product: Product) {
return this.http.post<Product>(this.baseUrl, product).pipe(
tap(() => this.getAll().subscribe()) // auto-refresh
);
}
update(product: Product) {
return this.http.put(`${this.baseUrl}/${product.id}`, product).pipe(
tap(() => this.getAll().subscribe())
);
}
delete(id: number) {
return this.http.delete(`${this.baseUrl}/${id}`).pipe(
tap(() => this.getAll().subscribe())
);
}
}
3. 🧩 Create Product Model
Create src/app/models/product.model.ts
:
export interface Product {
id?: number;
name: string;
description: string;
price: number;
}
4. 🧱 Generate Product Component
ng generate component components/product.component --standalone --flat
Then update src/app/components/product.component.ts
:
import { Component, OnInit } from '@angular/core';
import { ProductServices } from '../services/product.services';
import { Product } from '../models/product.model';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
@Component({
selector: 'app-product.component',
imports: [CommonModule, FormsModule],
templateUrl: './product.component.html',
styleUrl: './product.component.css'
})
export class ProductComponent implements OnInit {
get products() {
return this.productService.products();
}
newProduct: Product = { name: '', description: '', price: 0 };
selectedProduct: Product | null = null;
constructor(public productService: ProductServices) { }
ngOnInit() {
this.productService.getAll().subscribe();
}
save() {
if (this.selectedProduct) {
this.productService.update(this.selectedProduct).subscribe(() => this.selectedProduct = null);
} else {
this.productService.create(this.newProduct).subscribe(() => this.newProduct = { name: '', description: '', price: 0 });
}
}
edit(product: Product) {
this.selectedProduct = { ...product };
}
delete(id: number) {
this.productService.delete(id).subscribe();
}
}
5. 🖼️ Create Product Component Template
Create src/app/components/product.component.html
:
<h2>Product Manager</h2>
<form (ngSubmit)="save()">
<div *ngIf="selectedProduct; else newForm">
<input
type="text"
[(ngModel)]="selectedProduct.name"
name="name"
placeholder="Name"
required
/>
<input
type="text"
[(ngModel)]="selectedProduct.description"
name="description"
placeholder="Description"
required
/>
<input
type="number"
[(ngModel)]="selectedProduct.price"
name="price"
placeholder="Price"
required
/>
<button type="submit">Update</button>
</div>
<ng-template #newForm>
<input
type="text"
[(ngModel)]="newProduct.name"
name="name"
placeholder="Name"
required
/>
<input
type="text"
[(ngModel)]="newProduct.description"
name="description"
placeholder="Description"
required
/>
<input
type="number"
[(ngModel)]="newProduct.price"
name="price"
placeholder="Price"
required
/>
<button type="submit">Add</button>
</ng-template>
</form>
<ul>
<li *ngFor="let product of products">
{{ product.name }} - {{ product.price | currency }}
<button (click)="edit(product)">Edit</button>
<button (click)="delete(product.id!)">Delete</button>
</li>
</ul>
6. Add a Route for ProductComponent
In src/app/app.routes.ts
:
import { Routes } from '@angular/router';
import { ProductComponent } from './components/product.component';
export const routes: Routes = [
{
path: '',
component: ProductComponent
}
];
You can add a layout wrapper or more pages later — this makes
ProductComponent
your root view.
7. Update AppComponent Template
to show only Router View
In src/app/app.html
:
<router-outlet />
7. Update app/app.config.ts to add HttpClient
import { ApplicationConfig, provideBrowserGlobalErrorListeners, provideZoneChangeDetection } from '@angular/core';
import { provideRouter } from '@angular/router';
import { routes } from './app.routes';
import { provideHttpClient } from '@angular/common/http';
export const appConfig: ApplicationConfig = {
providers: [
provideBrowserGlobalErrorListeners(),
provideZoneChangeDetection({ eventCoalescing: true }),
provideRouter(routes),
provideHttpClient()
]
};
🔐 Bonus (Optional): Ignore SSL Certificate in Dev (for local HTTPS API)
If your browser/API complains about self-signed HTTPS certs, you can:
-
Allow self-signed certs in the browser
-
Or run ASP.NET Core backend with HTTP (
--urls http://localhost:5000
inProgram.cs
)
Final Testing Guide
1. Run the ASP.NET Core Backend
From the ProductApi
directory:
dotnet run
Ensure it's accessible at:
https://localhost:5104/swagger
Test the following endpoints via Swagger:
-
GET /api/products
-
POST /api/products
-
PUT /api/products/{id}
-
DELETE /api/products/{id}
If everything works here, your backend is ready.
2. Run the Angular 20 Frontend
From the product-frontend
directory:
ng serve
Open:
http://localhost:4200
You should see:
-
A form to create products
-
A list of existing products
-
Working Edit and Delete buttons
-
Data syncing automatically after every change
Make sure the browser does not block any API requests due to CORS or HTTPS certificate issues.
3. End-to-End Scenarios
Test the full workflow:
✅ Add a new product
✅ Edit a product
✅ Delete a product
✅ Refresh the page — data should persist
✅ Check the SQL Server database (e.g., using Azure Data Studio)
If all these work, your app is fully functional!
Conclusion
In this updated tutorial, we built a full-stack web app using:
-
ASP.NET Core 8.0 (with minimal APIs + Entity Framework Core)
-
Angular 20 (standalone components, Signals, HttpClient)
-
SQL Server (via Docker on macOS)
You learned how to:
-
Build a modern RESTful API with minimal boilerplate in .NET 8
-
Connect to SQL Server using EF Core
-
Build an Angular 20 app using standalone components and Signals
-
Enable CORS for local development
-
Deploy and test on macOS with Docker
This architecture is scalable, maintainable, and production-ready with some refinements like authentication and validation.
You can find the fully working source code on our GitHub.
That's just the basics. If you need more deep learning about ASP.NET Core, Angular, or related, you can take the following cheap course:
- ANGULAR and ASP. NET Core REST API - Real World Application
- Creating GraphQL APIs with ASP. Net Core for Beginners
- ASP .Net MVC Quick Start
- Master SignalR: Build Real-Time Web Apps with ASP. NET
- Fullstack Asp. Net Core MVC & C# Bootcamp With Real Projects
- ASP. NET Core MVC - A Step by Step Course
Thanks!