Build a Bug Tracker API with ASP.NET

Build a REST API with full CRUD operations using ASP.NET Core and C#.

Introduction

30 Second Summary

Every app you use daily has a backend server quietly handling requests behind the scenes. When you submit a form, tap a button, or load a feed, your device sends a request to a server that processes it and sends back a response.

In this project, you will build a Bug Tracker REST API from scratch using ASP.NET Core and C#. You will create endpoints that handle bug reports, persist data to a database, and return proper HTTP status codes.

What You'll Build

By the end of this project, you will have a running API on your machine where you can send HTTP requests and get back JSON responses with proper status codes, exactly like a real production backend.

By the end of this project, you'll have:

  • A REST API that lets you report new bugs with a title, description, priority, and status, then retrieve them individually or as a full list.
  • Full CRUD operations (Create, Read, Update, Delete) so you can update a bug's status as work progresses and delete bugs created by mistake.
  • Meaningful error handling that returns a 404 Not Found response when someone requests a bug that does not exist, proving your API communicates failures correctly.
  • Secret Mission: Add query filtering so you can retrieve only bugs matching a specific priority or status.

Are there any prerequisites?

You need basic programming knowledge in any language (Java or Python is fine). No prior C# or .NET experience is required. This project teaches you both as you build.

Before We Start

Before we dive into code, let's lock in what you're about to build and why it matters.

Set Up Your .NET Environment

Every backend project starts with a working development environment. Your ASP.NET Core API needs the .NET 8 SDK to compile and run C# code from the command line.

Visual Studio 2022 usually bundles the SDK, but we need to confirm it is there and working. In this step, you will verify the SDK, scaffold a starter project, and run it to see your first response.

In this step, get ready to:
  • Verify the .NET 8 SDK is installed on your machine.
  • Create the BugTrackerApi project using the .NET CLI.
  • Run the project and confirm it serves a response in the browser.
Verify your .NET 8 SDK installation

The .NET SDK includes the compiler, runtime, and CLI tools you need to build and run C# projects. Let's confirm it is present.

  • Press the Windows key (Windows) or Cmd+Space (macOS) to open your search bar.
  • Type PowerShell (Windows) or Terminal (macOS) and press Enter to open it.
  • Check your .NET SDK version by running this command:
dotnet --version

Look at the version number that appears in your terminal.

✔️ I see version 8 or higher

If you see a number starting with 8 (for example, 8.0.423), you are good to go. The .NET 8 SDK is installed and ready.

ⓧ I see an older version

If you see a version starting with 6 or 7, your SDK is too old for this project. Visual Studio 2022 version 17.8 or higher ships with .NET 8, so updating Visual Studio may fix this.

  • Open Visual Studio Installer and check for updates.
  • If no update is available, download the .NET 8 SDK directly from https://dotnet.microsoft.com/download/dotnet/8.0.
  • Run the installer and follow the prompts.
  • Close and reopen your terminal, then run dotnet --version again to confirm you now see a version starting with 8.

ⓧ Command not found

If your terminal says the command is not recognized, the .NET SDK is not installed.

What is the .NET SDK?

The SDK (Software Development Kit) includes the C# compiler, the runtime that executes your code, and CLI tools like dotnet new and dotnet run. It is everything you need to build and run .NET projects from the terminal.

Create the BugTrackerApi project

Now that the SDK is confirmed, you can scaffold your project. The dotnet new web command generates a minimal ASP.NET Core project with a single Program.cs file.

  • Move to your Desktop by running this command:
cd ~/Desktop
  • Create the project by running this command:
dotnet new web -o BugTrackerApi

You should see a confirmation message indicating the template was created successfully.

  • Move into the project folder by running:
cd BugTrackerApi

What did that command create?

The dotnet new web command creates a minimal ASP.NET Core project. Inside the BugTrackerApi folder you now have Program.cs, BugTrackerApi.csproj, appsettings.json, and a Properties folder.

The .csproj file tells .NET which framework to target (net8.0). Program.cs contains your entire application: a single route that returns "Hello World!" to any browser that visits it.

  • Open Visual Studio 2022. Press the Windows key (Windows) or Cmd+Space (macOS), type Visual Studio 2022, and press Enter.
  • Click File in the top menu bar.
  • Click Open, then Folder.
  • Navigate to your Desktop and select the BugTrackerApi folder, then click Select Folder.

You should see Program.cs and BugTrackerApi.csproj in the Solution Explorer panel on the right.

Don't see the files in Solution Explorer?

Make sure you selected the BugTrackerApi folder itself, not the Desktop folder. The folder should contain Program.cs directly inside it.

If the folder appears empty, try File > Open > Folder again and double-check the path. help me find my BugTrackerApi files in Visual Studio.

Run the project and see Hello World

Your project is ready to run. The dotnet run command compiles your code and starts a local web server so you can test it in the browser.

  • Start the server by running this command in your terminal (make sure you are inside the BugTrackerApi folder):
dotnet run

Your terminal displays a URL where the app is listening. Look for a line containing http://localhost: followed by a port number (somewhere between 5000 and 5300).

  • Copy the http://localhost:{port} URL from your terminal output.
  • Open a browser and paste the URL into the address bar.

Before you look at the page, what do you think the browser will show?

You should see the text Hello World! displayed in your browser. That text comes from the app.MapGet("/", () => "Hello World!"); line in Program.cs.

  • Return to your terminal and press Ctrl+C to stop the server.

What just happened?

When you ran dotnet run, the .NET SDK compiled your C# code and started a web server called Kestrel. Kestrel listened for HTTP requests on that port.

When your browser sent a GET request to /, the server matched it to the MapGet("/", ...) route and returned the string as the response. This is the request/response cycle in action.

Browser showing an error or blank page?

Make sure you copied the http:// URL (not https://) from the terminal. The minimal template does not configure HTTPS by default.

If the terminal shows an error instead of a URL, check that you are inside the BugTrackerApi folder (the one containing BugTrackerApi.csproj). Help me fix my dotnet run error.

✔️ Awesome, I've got everything!

Great. Double check you have saved all files and stopped the server with Ctrl+C.

ⓧ I'd like to double check the full code

Here is what your Program.cs should look like (this was generated by the template):

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.MapGet("/", () => "Hello World!");

app.Run();

And your BugTrackerApi.csproj should look like this:

<Project Sdk="Microsoft.NET.Sdk.Web">

  <PropertyGroup>
    <TargetFramework>net8.0</TargetFramework>
    <Nullable>enable</Nullable>
    <ImplicitUsings>enable</ImplicitUsings>
  </PropertyGroup>

</Project>

Your environment is ready and your first .NET server responded to a request. Next up, you will replace that single route with real bug-tracking endpoints that accept and return JSON data.

Create Your First Endpoints

Your .NET environment is ready and the default "Hello World!" endpoint is running. Now it's time to build something real.

Right now the app only has one route that returns a plain string. A real API needs multiple endpoints that handle structured data. In this step, you'll create endpoints that return JSON bug reports, accept new bugs from clients, and respond with proper HTTP status codes.

In this step, get ready to:
  • Define a Bug data model and create GET endpoints that return JSON.
  • Add a POST endpoint that accepts new bug reports from clients.
  • Test all endpoints using a .http file in Visual Studio.
Define the Bug data model

Before you can store or return bug reports, you need to define what a bug looks like in code. In C#, you do this with a class that lists the properties each bug has.

  • In Visual Studio 2022, open Program.cs from the Solution Explorer panel on the right.
  • Scroll to the very bottom of the file, below the app.Run(); line.
  • Add the Bug class by pasting the following code at the bottom:
// Data model - defines the shape of a bug report
public class Bug
{
    public int Id { get; set; }
    public string Title { get; set; } = string.Empty;
    public string Description { get; set; } = string.Empty;
    public string Priority { get; set; } = "Medium";
    public string Status { get; set; } = "Open";
    public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
}

What does this code do?

This class is your data model. It defines the shape of every bug report your API handles.

  • Each property (Id, Title, Priority, etc.) represents one piece of data about a bug.
  • The = string.Empty and = "Medium" parts set default values so a bug always has sensible data even if the client doesn't provide every field.
  • In C#, you can define classes below app.Run() and still use them above it. The compiler reads the whole file before running anything.
  • Save Program.cs and confirm the project builds by running this command in the terminal:
dotnet build

You should see a "Build succeeded" message with 0 errors.

Seeing build errors?

  • Check that the Bug class is outside the top-level statements area. It must be below app.Run(); with a blank line separating them.
  • Make sure every property line ends with a semicolon.
  • Confirm the curly braces around the class are balanced.

Help me fix build errors in my Bug class definition.

Add the GET endpoints

Now you'll replace the Hello World route with real endpoints. Each endpoint maps a URL to a piece of code that runs when someone visits that URL. This is called routing.

  • In Program.cs, find this line:
app.MapGet("/", () => "Hello World!");
  • Delete that line and replace it with the following code that creates a hardcoded list of bugs and two GET endpoints:
// Hardcoded list of bugs (temporary in-memory storage)
var bugs = new List<Bug>
{
    new Bug { Id = 1, Title = "Sample bug", Description = "A sample bug", Priority = "Medium", Status = "Open" }
};

// Return all bugs as JSON
app.MapGet("/bugs", () => Results.Ok(bugs));

// Return a specific bug by ID, or 404 if not found
app.MapGet("/bugs/{id}", (int id) =>
    bugs.FirstOrDefault(b => b.Id == id) is Bug bug
        ? Results.Ok(bug)
        : Results.NotFound());

What does this code do?

  • var bugs = new List<Bug> creates a list to hold bug objects. It starts with one sample bug so you can see data immediately.
  • app.MapGet("/bugs", ...) tells the server: when someone sends a GET request to /bugs, run this code and return the list as JSON with a 200 OK status code.
  • /bugs/{id} is a route parameter. When someone visits /bugs/1, the framework extracts 1 and passes it to your code as the id variable.
  • FirstOrDefault searches the list for a matching bug. If found, it returns 200 OK. If not, it returns 404 Not Found.
  • Save Program.cs and run the app:
dotnet run
  • Open your browser and navigate to the URL shown in your terminal output, appending /bugs to the end (for example, http://localhost:5000/bugs).

You should see a JSON array containing your sample bug.

  • Now change the URL to /bugs/1 in your browser.

You should see the single sample bug returned as a JSON object.

  • Try /bugs/999 in your browser.

You should see an empty response with no body. This is the 404 Not Found status proving your API correctly communicates that a bug doesn't exist.

Not seeing JSON in the browser?

  • Make sure you appended /bugs to the URL. The root path / no longer exists since you deleted the Hello World route.
  • Check the terminal for the correct port number. Your URL might use a different port than 5000.
  • Confirm you saved Program.cs before running dotnet run.

Why am I not seeing JSON when I visit /bugs in my browser?

Add a POST endpoint and create the test file

GET endpoints let clients read data. POST is how clients send new data to the server. A POST request carries data in its body, and the server processes it and responds.

  • Stop the server with Ctrl+C in the terminal.
  • In Program.cs, add the following POST endpoint below the app.MapGet("/bugs/{id}", ...) block:
// Accept a new bug from the client and add it to the list
app.MapPost("/bugs", (Bug bug) =>
{
    bug.Id = bugs.Count > 0 ? bugs.Max(b => b.Id) + 1 : 1;
    bugs.Add(bug);
    return Results.Created($"/bugs/{bug.Id}", bug);
});

What does this code do?

  • app.MapPost("/bugs", ...) handles POST requests to /bugs. The framework automatically converts the JSON body into a Bug object. This is called JSON serialization.
  • The ID assignment line calculates the next available ID by finding the current maximum and adding 1.
  • Results.Created(...) returns a 201 Created status code. This tells the client "I made a new thing, and here's where to find it."

You can't test POST endpoints in a browser because browsers only send GET requests when you type a URL. You need a tool that lets you craft custom HTTP requests.

  • Create a new file called BugTrackerApi.http in your project root. In Visual Studio, right-click the project in Solution Explorer, select Add, then New Item.
  • Choose HTTP File from the list and name it BugTrackerApi.http.
  • Replace any default content with the following:
@host = http://localhost:5000

### Get all bugs
GET {{host}}/bugs

### Get bug by ID
GET {{host}}/bugs/1

### Create a new bug
POST {{host}}/bugs
Content-Type: application/json

{
  "title": "Login button unresponsive",
  "description": "Clicking the login button does nothing on mobile Safari",
  "priority": "High",
  "status": "Open"
}

What is a .http file?

A .http file is a built-in way to test APIs directly from Visual Studio 2022. Each ### line separates a different request. The @host variable at the top lets you change the base URL in one place.

Visual Studio shows a Send Request link above each request. Click it to fire the request and see the response in a pane below.

Check your port number

If your terminal showed a port other than 5000 when you ran the app earlier, update the @host line in BugTrackerApi.http to match your actual port.

  • Save both Program.cs and BugTrackerApi.http, then start the app:
dotnet run

Before you test, predict this: if you send the POST request to create a new bug and then GET /bugs, how many bugs do you expect to see in the response?

  • In BugTrackerApi.http, click the Send Request link above the POST request ("Create a new bug").

You should see a 201 Created response with the new bug's JSON, including an auto-assigned ID of 2.

  • Now click Send Request above the first GET request ("Get all bugs").

You should see both bugs in the response: the original sample bug and the new one you just created.

  • Click Send Request above "Get bug by ID" to confirm GET /bugs/1 returns just the sample bug.

POST returning an error?

  • Confirm the app is running. You must start dotnet run before sending requests from the .http file.
  • Check that the @host port in your .http file matches the port shown in your terminal.
  • Make sure there is a blank line between Content-Type: application/json and the JSON body. This blank line is required by the HTTP protocol.

Help me fix my POST request in the .http file.

What just happened?

You've completed a full request/response cycle. A client sent a request, the server routed it to the correct handler based on the URL and HTTP method, your code processed the data, and the server sent back a response with the appropriate status code.

This is the exact pattern behind every backend: route the request, run some logic, return a response.

  • Stop the server with Ctrl+C.

✔️ Awesome, I've got everything!

Great. Double check you've saved both Program.cs and BugTrackerApi.http.

ⓧ I'd like to double check the full code

Your Program.cs should contain these sections in order. Scroll up and confirm each one is present:

  • The builder and app setup (var builder and var app lines).
  • The var bugs = new List<Bug> initialization with one sample bug.
  • app.MapGet("/bugs", ...) returning all bugs.
  • app.MapGet("/bugs/{id}", ...) returning one bug or 404.
  • app.MapPost("/bugs", ...) creating a new bug with an auto-assigned ID.
  • app.Run(); at the bottom of the top-level code.
  • The Bug class definition below app.Run().

Your BugTrackerApi.http should match this exactly:

@host = http://localhost:5000

### Get all bugs
GET {{host}}/bugs

### Get bug by ID
GET {{host}}/bugs/1

### Create a new bug
POST {{host}}/bugs
Content-Type: application/json

{
  "title": "Login button unresponsive",
  "description": "Clicking the login button does nothing on mobile Safari",
  "priority": "High",
  "status": "Open"
}

Your API now handles three different requests: listing all bugs, fetching one by ID, and creating new bugs. Next up, you'll connect a database so your data survives a server restart.

Connect a Database with Entity Framework Core

In Step 2, you built endpoints that create and retrieve bugs. But you may have noticed a frustrating problem: every time you stop the server and restart it, all your bugs vanish.

That data loss happens because your bugs live in a plain C# List that only exists while the process runs. Entity Framework Core solves this by giving your app a proper database layer that persists data across requests within a server session.

In this step, get ready to:
  • Install the Entity Framework Core InMemory package.
  • Create a database context class to manage bug storage.
  • Replace your List-based endpoints with database-backed versions.
Install Entity Framework Core and create the database context

Entity Framework Core is a library that lets your C# code talk to a database without writing raw SQL. The InMemory variant gives you a lightweight database that lives in your app's memory.

  • Make sure your server is stopped (press Ctrl+C in the terminal if it is still running).
  • Install the Entity Framework Core InMemory package by running this command:
dotnet add package Microsoft.EntityFrameworkCore.InMemory

What does this command do?

This downloads the Microsoft.EntityFrameworkCore.InMemory NuGet package (version 8.0.26) and adds it to your project. NuGet is the package manager for .NET, similar to npm for JavaScript or pip for Python.

  • Confirm the package was added by opening BugTrackerApi.csproj in Visual Studio 2022.
  • You should see a new <ItemGroup> section with a PackageReference for Microsoft.EntityFrameworkCore.InMemory.

Command not recognized?

Make sure you are running the command from inside the BugTrackerApi folder (where the .csproj file lives). If you are one level up, run cd BugTrackerApi first.

help me troubleshoot why dotnet add package is not working

Now you need to create a database context class. This is the bridge between your C# code and the database.

  • Open Program.cs in Visual Studio 2022.
  • Scroll to the bottom of the file, below the Bug class closing brace.
  • Add the following database context class below the Bug class:
// Database context
public class BugDb : DbContext
{
    public BugDb(DbContextOptions<BugDb> options) : base(options) { }
    public DbSet<Bug> Bugs => Set<Bug>();
}

What does this code do?

BugDb extends DbContext, which is Entity Framework Core's main class for managing database operations. Think of it as a translator between your C# objects and database tables.

The DbSet<Bug> Bugs property tells Entity Framework to create a table called Bugs that stores Bug objects. Each property on your Bug class becomes a column in that table.

Next, you need to wire the database into your app's startup. This requires two changes at the top of Program.cs.

  • Scroll to the very top of Program.cs.
  • Add this using statement as the first line of the file (above var builder):
using Microsoft.EntityFrameworkCore;
  • Find the line var builder = WebApplication.CreateBuilder(args); and add this line directly below it:
builder.Services.AddDbContext<BugDb>(opt => opt.UseInMemoryDatabase("BugList"));

What does this registration do?

AddDbContext registers your BugDb class with the app's dependency injection system. This means any endpoint that asks for a BugDb parameter automatically receives a ready-to-use database connection.

UseInMemoryDatabase("BugList") tells Entity Framework to store everything in memory rather than an external database server. Fast to set up, no extra software needed.

  • Save Program.cs.
  • Verify everything compiles by running:
dotnet build
  • You should see Build succeeded in the output. If you see errors, double check the using statement is at the very top and the BugDb class is outside the Bug class (not nested inside it).

Seeing a build error?

If you see an error about DbContext not being found, make sure the using Microsoft.EntityFrameworkCore; line is at the very top of Program.cs. If you see an error about BugDb, confirm the class is placed after the closing brace of the Bug class, not inside it.

help me fix my Entity Framework Core build error

Replace your endpoints with database-backed versions

Your endpoints currently read from and write to a plain List<Bug>. Now you will swap them to use the database context instead.

  • In Program.cs, find and delete the entire var bugs = new List<Bug> block (the list and its sample bug):
var bugs = new List<Bug>
{
    new Bug { Id = 1, Title = "Sample bug", Description = "A sample bug", Priority = "Medium", Status = "Open" }
};
  • Delete all three existing endpoint blocks (app.MapGet("/bugs", ...), app.MapGet("/bugs/{id}", ...), and app.MapPost("/bugs", ...)).
  • Replace them with these database-backed endpoints between var app = builder.Build(); and app.Run();:
// GET all bugs
app.MapGet("/bugs", async (BugDb db) =>
    Results.Ok(await db.Bugs.ToListAsync()));

// GET bug by ID
app.MapGet("/bugs/{id}", async (int id, BugDb db) =>
    await db.Bugs.FindAsync(id)
        is Bug bug
            ? Results.Ok(bug)
            : Results.NotFound());

// POST create a new bug
app.MapPost("/bugs", async (Bug bug, BugDb db) =>
{
    db.Bugs.Add(bug);
    await db.SaveChangesAsync();
    return Results.Created($"/bugs/{bug.Id}", bug);
});

What changed in these endpoints?

Each endpoint now takes a BugDb db parameter. ASP.NET Core sees this and automatically provides a database instance. This is dependency injection in action. You never call new BugDb() yourself.

The endpoints are now async because database operations take time. await db.Bugs.ToListAsync() fetches all bugs. db.Bugs.Add(bug) queues an insert. await db.SaveChangesAsync() commits it to the database.

  • Save Program.cs.

Not sure what to delete?

Everything between var app = builder.Build(); and app.Run(); should be replaced. The old var bugs list and the three old MapGet/MapPost blocks all go away. The new three endpoints take their place.

help me figure out which code to delete and replace in Program.cs

✔️ Awesome, I've got everything!

Great. Make sure you have saved Program.cs before moving on.

ⓧ I'd like to double check the full code

Your Program.cs should now have these sections in order. Scroll through your file and confirm each one is present:

  • Line 1: using Microsoft.EntityFrameworkCore; statement.
  • Builder section: var builder followed by builder.Services.AddDbContext<BugDb>(...) then var app = builder.Build();.
  • Three async endpoints: GET /bugs, GET /bugs/{id}, POST /bugs. Each takes BugDb db as a parameter.
  • app.Run(); at the end of the main code.
  • The Bug class (unchanged from before).
  • The new BugDb class at the very bottom.

Make sure the old var bugs = new List<Bug>{...} block is completely gone. No trace of it should remain.

Run and test your database-backed API

Time to see the database in action. You will create bugs and confirm they persist across multiple requests.

  • Start your server by running:
dotnet run
  • Note the URL in the terminal output (e.g. http://localhost:5000). Open that URL with /bugs appended in your browser.
  • You should see an empty JSON array [] because the database starts fresh with no data.

Why is the list empty now?

Previously your code hardcoded a sample bug in the List. Now the database starts empty, which is the correct behavior. You will add bugs through your POST endpoint instead of hardcoding them.

  • Send a POST request to create a bug. In Visual Studio 2022, open BugTrackerApi.http and click the Send Request link above the POST {{host}}/bugs section.
  • You should get a 201 Created response with the bug JSON including an auto-generated Id.
  • Now send the GET {{host}}/bugs request from the .http file (or refresh /bugs in your browser).
  • You should see your bug in the JSON array. POST another bug with different details and GET /bugs again to confirm both appear.

Your data now persists across multiple requests within the same server session. Before you celebrate, try one more test.

What do you think will happen if you stop the server and restart it? Will your bugs still be there?

  • Press Ctrl+C in the terminal to stop the server.
  • Start it again by running:
dotnet run
  • Open /bugs in your browser again.

You see an empty array []. The bugs you created are gone. This is expected behavior for an in-memory database. It resets every time the server stops.

Why did the data disappear?

An in-memory database lives only in the server process. When the process ends, the database goes with it. This is fine for development and testing.

The key improvement over the raw List<Bug> approach: with Entity Framework Core, your data persists correctly across all requests during a session. The database handles concurrency, querying, and data integrity. A real production app would swap UseInMemoryDatabase for a real database like SQL Server or PostgreSQL with a single line change.

Your API now has a real database layer. Next up, you will add PUT and DELETE endpoints to complete the full set of CRUD operations.

Complete CRUD with Proper Status Codes

Your API can create bugs and read them back, but it has no way to mark a bug as resolved or remove one filed by mistake.

A real API needs all four CRUD operations: Create, Read, Update, and Delete. This step adds UPDATE and DELETE endpoints that return meaningful HTTP status codes so clients know exactly what happened with each request.

In this step, get ready to:
  • Add a PUT endpoint that updates a bug's fields and returns 204 No Content.
  • Add a DELETE endpoint that removes a bug and returns 204 No Content.
  • Test error responses by requesting a bug that does not exist and confirming you get 404 Not Found.
Add a PUT endpoint to update bugs

When a developer fixes a bug, the team needs to change its status from Open to InProgress or Resolved. The PUT method is the HTTP standard for replacing or updating an existing resource.

  • In Program.cs, find the closing }); of the POST endpoint.
  • Add the PUT endpoint below it by pasting this code:
// PUT update an existing bug
app.MapPut("/bugs/{id}", async (int id, Bug input, BugDb db) =>
{
    var bug = await db.Bugs.FindAsync(id);

    if (bug is null) return Results.NotFound();

    bug.Title = input.Title;
    bug.Description = input.Description;
    bug.Priority = input.Priority;
    bug.Status = input.Status;

    await db.SaveChangesAsync();

    return Results.NoContent();
});

What does this code do?

  • FindAsync(id) looks up the bug in the database by its ID.
  • If the bug does not exist, Results.NotFound() returns a 404 status code to the client.
  • If found, each field is updated with the values from the incoming JSON body (input).
  • Results.NoContent() returns 204, which means "success, but I have nothing to send back."

Now add a test request to your .http file so you can try the PUT endpoint.

  • Open BugTrackerApi.http and add the following below the existing POST request:
### Update a bug
PUT {{host}}/bugs/1
Content-Type: application/json

{
  "title": "Login button unresponsive",
  "description": "Clicking the login button does nothing on mobile Safari",
  "priority": "High",
  "status": "InProgress"
}

What changed in this request?

The JSON body is almost identical to the POST request, but status is now InProgress instead of Open. This simulates a developer picking up the bug and starting work on it.

  • Save both files.
  • Start the server by running:
dotnet run
  • First, send the POST request in your .http file to create a bug (you need a bug to exist before you can update it).
  • Then send the PUT request. You should see a 204 No Content response with an empty body.
  • Send the GET /bugs/1 request to confirm the status field now says InProgress.

Seeing a 404 on your PUT request?

You need to create a bug first. Send the POST request before the PUT. The in-memory database starts empty every time the server restarts.

Also check that the URL in your PUT request targets /bugs/1 (matching the ID the POST assigned).

help me debug my PUT endpoint returning 404

  • Stop the server with Ctrl+C.
Add a DELETE endpoint to remove bugs

Sometimes a bug report is a duplicate or was filed by mistake. The DELETE method removes a resource permanently.

  • In Program.cs, find the closing }); of the PUT endpoint you just added.
  • Add the DELETE endpoint below it by pasting this code:
// DELETE a bug
app.MapDelete("/bugs/{id}", async (int id, BugDb db) =>
{
    if (await db.Bugs.FindAsync(id) is Bug bug)
    {
        db.Bugs.Remove(bug);
        await db.SaveChangesAsync();
        return Results.NoContent();
    }

    return Results.NotFound();
});

What does this code do?

  • The is Bug bug pattern combines the lookup and null check in one line. If FindAsync returns a bug, it gets assigned to the bug variable.
  • db.Bugs.Remove(bug) marks the bug for deletion. SaveChangesAsync() commits the removal to the database.
  • If no bug is found with that ID, the endpoint returns 404 Not Found.

✔️ Awesome, I've got everything!

Great. Double check you have saved Program.cs.

ⓧ I'd like to double check the full code

Your Program.cs should now have these sections in order. Scroll up and confirm each one is present:

  • The using statement, builder setup, and AddDbContext registration at the top.
  • The GET /bugs endpoint returning all bugs.
  • The GET /bugs/{id} endpoint returning one bug or 404.
  • The POST /bugs endpoint creating a bug and returning 201 Created.
  • The PUT /bugs/{id} endpoint updating fields and returning 204 or 404.
  • The DELETE /bugs/{id} endpoint removing a bug and returning 204 or 404.
  • The app.Run() call, then the Bug class and BugDb class at the bottom.

Now add the DELETE test request to your .http file.

  • Open BugTrackerApi.http and add this below the PUT request you added earlier:
### Delete a bug
DELETE {{host}}/bugs/1
  • Save BugTrackerApi.http.
  • Start the server again by running:
dotnet run
  • Test the full lifecycle. Send these requests in order using your .http file:
  • POST /bugs to create a bug. You see a 201 response with the bug's data.
  • PUT /bugs/1 to change the status to InProgress. You see a 204 response.
  • GET /bugs/1 to confirm the status is now InProgress.
  • DELETE /bugs/1 to remove the bug. You see a 204 response.

Not seeing 204 on DELETE?

Make sure you created a bug first with POST. The in-memory database resets every time the server restarts, so bugs from your last session are gone.

Check that your DELETE endpoint code uses db.Bugs.Remove(bug) followed by await db.SaveChangesAsync(). Both lines are required.

help me debug my DELETE endpoint

Test the error path

A well-built API does not just handle the happy path. It must tell clients clearly when something goes wrong.

Before you run this next request, think about what should happen when you ask for a bug that does not exist. What status code would the server return?

  • With the server still running, open your browser and navigate to:
http://localhost:5000/bugs/999

You see a 404 Not Found response. No bug has ID 999, so your API correctly communicates that the resource does not exist.

  • Now try the same thing with PUT. In your .http file, temporarily change the PUT request URL from /bugs/1 to /bugs/999 and send it.
  • You see 404 Not Found again. The PUT endpoint cannot update a bug that does not exist.
  • Change the URL back to /bugs/1 when you are done.

Why do status codes matter?

Status codes are the universal language between servers and clients. A frontend app reads the status code to decide what to show the user.

200 means success with data. 201 means a new resource was created. 204 means success with no body. 404 means the resource was not found. Every API you will ever build uses these same codes.

  • Stop the server with Ctrl+C.

Your API now handles the full lifecycle of a bug report. You can create, read, update, and delete bugs, and your server responds with the correct HTTP status code every time. Next up, you will extend this API with query filtering in the Secret Mission.

Secret mission

Add Query Filtering

Real APIs rarely return every record. Your team has 50 bugs tracked but only cares about the Critical ones right now. Can you add query string filtering so clients can ask for exactly what they need?

Clean Up Your Resources

Clean Up Your Resources

Decide whether to keep your resources running, pause them to come back later, or delete them entirely.

This project runs entirely locally, so there are no ongoing costs. The in-memory database disappears the moment you stop the server. The only thing on disk is the project folder itself.

Resources you used:

  • The BugTrackerApi project folder on your Desktop (includes Program.cs, BugTrackerApi.http, BugTrackerApi.csproj, and NuGet packages).

Keep everything running

No action needed. Choose this if you are still actively building or want to reference the code later.

  • Your BugTrackerApi folder stays on your Desktop for future experimentation.
  • You can restart the API any time by opening a terminal in the project folder and running dotnet run.

Pause - I'll come back to this later

Stop the running server to free up your terminal. Your project files remain on disk for when you return.

  • If the server is still running in your terminal, press Ctrl+C to stop it.
  • Close the project in Visual Studio 2022 if you want to free up memory.
  • The BugTrackerApi folder stays on your Desktop. Reopen it any time to pick up where you left off.

Delete - I don't want to use this again

Remove all project files from your machine. This is permanent.

  • If the server is still running, press Ctrl+C to stop it.
  • Close the project in Visual Studio 2022.
  • Delete the project folder by running the command for your operating system.

On macOS or Linux:

rm -rf ~/Desktop/BugTrackerApi

On Windows (PowerShell):

Remove-Item -Recurse -Force ~\Desktop\BugTrackerApi
  • Confirm the folder is gone by checking your Desktop. You should no longer see a BugTrackerApi folder.

That's a Wrap!

That's a Wrap!

Nice work! You just built a fully functional REST API from scratch using ASP.NET Core Minimal APIs and .NET 8.

You've learned how to:

  • Built a complete REST API with full CRUD operations (Create, Read, Update, Delete) and proper HTTP status codes (200, 201, 204, 404).
  • Traced how an HTTP request travels from client to server, gets routed to a handler, hits a database, and returns a JSON response.
  • Modeled data with a C# class and persisted it using Entity Framework Core with dependency injection providing the database context automatically to your route handlers.
  • Secret Mission: Added query string filtering to return subsets of bugs based on priority and status.

Ready to quiz yourself?