
Learn how to build a REST API with ASP.NET Core 8 step by step. Full C# code, CRUD, EF Core, and best practices. Start building your Web API today!
Want to build a REST API with ASP.NET Core? You're in the right place. In this step-by-step C# REST API tutorial, you'll create a fully working ASP.NET Core 8 Web API from scratch — complete with CRUD endpoints, Entity Framework Core, dependency injection, and the best practices senior .NET developers use in production. By the end, you'll have a runnable API and a clear understanding of why each piece matters, not just how to wire it up.
ASP.NET Core 8 is one of the fastest web frameworks available today, and its minimal API model makes spinning up endpoints faster than ever. Whether you're a beginner searching "how to create a Web API in .NET 8" or an intermediate developer looking for best practices, this guide has you covered.
What You'll Need to Build a REST API with ASP.NET Core 8
Before we write any code, make sure you have the following installed:
- .NET 8 SDK — download from the official Microsoft site.
- An IDE — Visual Studio 2022, VS Code, or JetBrains Rider.
- Basic knowledge of C# and HTTP verbs (GET, POST, PUT, DELETE).
Verify your installation by running this command in your terminal:
dotnet --version
# Should output 8.x.x
Step 1: Create the ASP.NET Core 8 Web API Project
Open your terminal and scaffold a new Web API project. The webapi template gives you a ready-to-run starting point with Swagger/OpenAPI support already configured.
dotnet new webapi -n ProductApi
cd ProductApi
dotnet run
This generates a project and launches it on a local port (e.g. https://localhost:7090). Navigate to /swagger in your browser and you'll see interactive API documentation out of the box. That's the power of ASP.NET Core 8 — a working API in under a minute.
Step 2: Define Your Model
Every REST API revolves around resources. Our resource is a Product. Create a new file Models/Product.cs. We use a model class to give EF Core and the serializer a strongly-typed shape to work with.
namespace ProductApi.Models;
public class Product
{
public int Id { get; set; }
public string Name { get; set; } = string.Empty;
public decimal Price { get; set; }
public int Stock { get; set; }
}
Why: Initializing Name to string.Empty avoids nullable reference warnings, which are enabled by default in .NET 8. This small habit prevents a whole class of NullReferenceException bugs.
Step 3: Set Up Entity Framework Core for CRUD
To persist data, we'll use Entity Framework Core CRUD with an in-memory database for this tutorial (swap it for SQL Server or PostgreSQL in production). Install the required packages:
dotnet add package Microsoft.EntityFrameworkCore.InMemory
Create a database context at Data/AppDbContext.cs. The DbContext is the bridge between your C# objects and the database.
using Microsoft.EntityFrameworkCore;
using ProductApi.Models;
namespace ProductApi.Data;
public class AppDbContext : DbContext
{
public AppDbContext(DbContextOptions<AppDbContext> options)
: base(options) { }
public DbSet<Product> Products => Set<Product>();
}
Step 4: Register Services with Dependency Injection
ASP.NET Core has dependency injection (DI) built in. Open Program.cs and register your DbContext. DI lets the framework manage object lifetimes for you — a core reason ASP.NET Core apps stay testable and maintainable.
using Microsoft.EntityFrameworkCore;
using ProductApi.Data;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContext<AppDbContext>(opt =>
opt.UseInMemoryDatabase("ProductsDb"));
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
var app = builder.Build();
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseHttpsRedirection();
Step 5: Build the CRUD Endpoints (Minimal API)
Now the heart of the tutorial. The minimal API ASP.NET Core approach lets you map HTTP routes directly to handler functions — clean, fast, and ideal for microservices. Add these endpoints to Program.cs before app.Run():
using ProductApi.Models;
// GET all products
app.MapGet("/api/products", async (AppDbContext db) =>
await db.Products.ToListAsync());
// GET a single product by id
app.MapGet("/api/products/{id}", async (int id, AppDbContext db) =>
await db.Products.FindAsync(id) is Product product
? Results.Ok(product)
: Results.NotFound());
// POST create a new product
app.MapPost("/api/products", async (Product product, AppDbContext db) =>
{
db.Products.Add(product);
await db.SaveChangesAsync();
return Results.Created($"/api/products/{product.Id}", product);
});
// PUT update an existing product
app.MapPut("/api/products/{id}", async (int id, Product input, AppDbContext db) =>
{
var product = await db.Products.FindAsync(id);
if (product is null) return Results.NotFound();
product.Name = input.Name;
product.Price = input.Price;
product.Stock = input.Stock;
await db.SaveChangesAsync();
return Results.NoContent();
});
// DELETE a product
app.MapDelete("/api/products/{id}", 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();
});
app.Run();
Why these status codes matter: A well-designed REST API speaks HTTP correctly. 201 Created signals a new resource and returns its location, 204 No Content confirms a successful update or delete with no body, and 404 Not Found tells clients the resource doesn't exist. Returning the right status code is the difference between an amateur and a professional Web API.
Step 6: Test Your REST API
Run the project again with dotnet run and open Swagger UI. Try creating a product with a POST request:
{
"name": "Wireless Mouse",
"price": 24.99,
"stock": 150
}
You can also test from the command line with curl:
curl -X POST https://localhost:7090/api/products \
-H "Content-Type: application/json" \
-d '{"name":"Wireless Mouse","price":24.99,"stock":150}'
Best Practices When You Build a REST API with ASP.NET Core
Now that the API works, here's how to take it to production quality:
- Use DTOs, not entities, in responses. Exposing EF Core entities directly leaks your database schema and risks over-posting attacks. Map to a
ProductDtoinstead. - Add input validation. Use data annotations or FluentValidation so clients can't submit negative prices or empty names.
- Implement async all the way. Every database call above uses
async/await— this keeps threads free and lets your API handle thousands of concurrent requests. - Version your API. Prefix routes with
/api/v1/so you can evolve without breaking existing clients. - Return consistent error responses. Use
ProblemDetails(RFC 7807), which ASP.NET Core 8 supports natively viaAddProblemDetails(). - Add pagination to list endpoints so a GET on thousands of records doesn't crush performance.
Common Pitfalls to Avoid
- Forgetting HTTPS redirection in production — always enforce TLS.
- Blocking calls like
.Resultor.Wait()on async methods, which cause deadlocks. - Returning 200 OK for everything — clients rely on accurate status codes.
- Leaving Swagger enabled in production unless you intentionally want public docs.
Controllers vs. Minimal APIs: Which Should You Use?
This tutorial used minimal APIs because they're concise and perfect for small-to-medium services. For larger applications with complex routing, filters, and model binding, traditional controllers (using [ApiController] and ControllerBase) offer more structure. The good news: both are first-class citizens in ASP.NET Core 8, and you can mix them in the same project.
Conclusion: You Just Built a REST API with ASP.NET Core 8
Congratulations — you now know how to build a REST API with ASP.NET Core from an empty folder to a fully functional CRUD service. Let's recap the key takeaways:
- Scaffold quickly with
dotnet new webapiand the built-in Swagger UI. - Model your resources and persist them with Entity Framework Core CRUD operations.
- Use the clean minimal API ASP.NET Core syntax to map HTTP verbs to handlers.
- Return correct HTTP status codes —
201,204, and404— to build a professional Web API. - Apply best practices like DTOs, async, validation, and API versioning before shipping to production.
The next step is to swap the in-memory database for SQL Server, add JWT authentication, and deploy to Azure or Docker. With these fundamentals, you have everything you need to build production-grade APIs in .NET 8. Bookmark this C# REST API tutorial and start building today!
Your go-to resource for C#, .NET, and modern software development. Follow along for daily tutorials, tips, and real-world examples.
Comments
Post a Comment