.NET EF Core Tutorial: Build a REST API in 3 Steps 2025
Ready to build a powerful REST API with .NET 8 and EF Core? This 2025 guide shows you how in just 3 simple, practical steps. Start coding today!
Daniel Ivanov
Senior .NET developer specializing in building scalable, high-performance web APIs.
Tired of complex setups and boilerplate code just to get a simple API running? You're not alone. The .NET ecosystem has evolved, and building powerful, data-driven applications is faster and more intuitive than ever.
Welcome to your 2025 guide to building a REST API with .NET 8 and Entity Framework Core. Forget the fluff. We're going to build a fully functional CRUD (Create, Read, Update, Delete) API in just three straightforward steps. By the end of this tutorial, you'll have a solid foundation you can expand on for your own projects.
Prerequisites
Before we dive in, make sure you have the following installed:
- .NET 8 SDK: You can download it from the official Microsoft website.
- A Code Editor: Visual Studio 2022, VS Code with the C# Dev Kit, or JetBrains Rider will all work perfectly.
- An API Client (Optional): Tools like Postman or Insomnia are great, but we can also use the built-in Swagger UI that comes with the project template.
Step 1: Project Setup & Database Context
First things first, let's create our project and set up the connection to our database. We'll be using SQLite because it's file-based and requires zero configuration, making it perfect for development and tutorials.
Open your terminal or command prompt and run the following command:
dotnet new webapi -n ProductApi
This command creates a new .NET Web API project in a folder named ProductApi
. Navigate into your new project directory:
cd ProductApi
Next, we need to install the necessary Entity Framework Core packages. We need one for SQLite and one for the design-time tools (which we'll use for migrations).
dotnet add package Microsoft.EntityFrameworkCore.Sqlite
dotnet add package Microsoft.EntityFrameworkCore.Design
Creating the Data Model
Every API needs data. Let's define the 'shape' of our data by creating a simple C# class. In your project, create a new file named Product.cs
and add the following code:
public class Product
{
public int Id { get; set; }
public required string Name { get; set; }
public decimal Price { get; set; }
public DateTime CreatedDate { get; set; }
}
This simple class defines a product with an ID, a required name, a price, and a creation date. This will become a table in our database.
Defining the DbContext
The DbContext
is the magic link between your application code and the database. It represents a session with the database and allows you to query and save instances of your entity classes. Create a new file named ApiDbContext.cs
:
using Microsoft.EntityFrameworkCore;
public class ApiDbContext : DbContext
{
public ApiDbContext(DbContextOptions<ApiDbContext> options)
: base(options)
{
}
public DbSet<Product> Products { get; set; }
}
Here, we're telling EF Core that we have a collection of Product
objects that we want to treat as a database table called Products
.
Wiring It All Up
Now, we need to tell our application about the ApiDbContext
and how to connect to the database. We do this in Program.cs
through dependency injection.
Open your Program.cs
file and add the following lines right after the var builder = WebApplication.CreateBuilder(args);
line:
// Add this using statement at the top of Program.cs
using Microsoft.EntityFrameworkCore;
// Add the DbContext to the services container.
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection") ?? "Data Source=products.db";
builder.Services.AddDbContext<ApiDbContext>(options =>
options.UseSqlite(connectionString));
This code tells the application to use our ApiDbContext
and configure it to connect to a SQLite database file named products.db
. If a connection string named "DefaultConnection" exists in our configuration (like appsettings.json
), it will use that instead.
Step 2: Creating the Database with Migrations
We've defined our data model in C#, but how does the actual database get created? That's where EF Core Migrations come in. Migrations translate your C# models into SQL scripts that create or update your database schema.
In your terminal, run the following command:
dotnet ef migrations add InitialCreate
This command inspects your DbContext
and models, compares them to the last migration (there isn't one yet), and generates the C# code necessary to create the database schema. You'll see a new Migrations
folder appear in your project.
Now, apply this migration to the database:
dotnet ef database update
This command executes the migration, creating the products.db
file and the Products
table inside it. Just like that, your database is ready!
Step 3: Building the API Endpoints
With our database ready, it's time for the main event: creating the API endpoints. We'll use .NET 8's Minimal APIs, which are clean, fast, and perfect for this task. We'll add endpoints for all the basic CRUD operations at the bottom of Program.cs
, right before app.Run();
.
First, let's define a group for our product endpoints to keep things organized.
var productsGroup = app.MapGroup("/products");
GET /products - Fetch All Products
This endpoint will return a list of all products in the database.
productsGroup.MapGet("/", async (ApiDbContext db) =>
await db.Products.ToListAsync());
GET /products/{id} - Fetch a Single Product
This endpoint retrieves a specific product by its ID. If the product isn't found, it returns a 404 Not Found status.
productsGroup.MapGet("/{id}", async (int id, ApiDbContext db) =>
await db.Products.FindAsync(id)
is Product product
? Results.Ok(product)
: Results.NotFound());
POST /products - Create a New Product
This endpoint accepts a new product in the request body, adds it to the database, and returns the newly created product with a 201 Created status.
productsGroup.MapPost("/", async (Product product, ApiDbContext db) =>
{
product.CreatedDate = DateTime.UtcNow;
db.Products.Add(product);
await db.SaveChangesAsync();
return Results.Created($"/products/{product.Id}", product);
});
PUT /products/{id} - Update an Existing Product
This endpoint updates an existing product. It finds the product by ID, updates its properties, and saves the changes.
productsGroup.MapPut("/{id}", async (int id, Product inputProduct, ApiDbContext db) =>
{
var product = await db.Products.FindAsync(id);
if (product is null) return Results.NotFound();
product.Name = inputProduct.Name;
product.Price = inputProduct.Price;
await db.SaveChangesAsync();
return Results.NoContent();
});
DELETE /products/{id} - Delete a Product
Finally, this endpoint removes a product from the database.
productsGroup.MapDelete("/{id}", async (int id, ApiDbContext db) =>
{
if (await db.Products.FindAsync(id) is Product product)
{
db.Products.Remove(product);
await db.SaveChangesAsync();
return Results.Ok(product);
}
return Results.NotFound();
});
Putting It All to the Test
Your API is now complete! The easiest way to test it is by running the application:
dotnet run
Now, open your browser and navigate to the URL provided in the console, adding /swagger
to the end (e.g., https://localhost:7123/swagger
). You'll see the beautiful Swagger UI, which documents and lets you test your API endpoints directly.
Try it out!
- Expand the POST /products endpoint, click "Try it out", fill in a name and price, and click "Execute". You should get a 201 response.
- Now, expand the GET /products endpoint, click "Try it out", and "Execute". You'll see the product you just created in the response!
From Zero to API Hero
And there you have it! In just three steps, you've built a modern, high-performance REST API using .NET 8 and Entity Framework Core. You've set up a project, modeled your data, created a database schema with migrations, and implemented a full set of CRUD endpoints.
This simple but powerful foundation is the starting point for almost any backend application. From here, you could explore:
- Validation: Using libraries like FluentValidation to validate incoming data.
- Repository Pattern: Abstracting your data access logic for better testability.
- Authentication & Authorization: Securing your endpoints with JWTs.
- Docker: Containerizing your new API for easy deployment.
Happy coding!