top of page

Your First .NET API in 30 Minutes

  • ShiftQuality Contributor
  • Mar 18
  • 6 min read

You know what .NET is. You know core C# syntax. Now you need to build something that talks to the outside world.

A REST API is how most modern software communicates. Your mobile app talks to an API. Your frontend talks to an API. Other services talk to your API. If you are building anything beyond a console application, this is the skill that unlocks real-world development.

This post gets you from zero to a running API with multiple endpoints. No theory dumps. You will type commands, see results, and understand what the code is doing at each step.

Create the Project

Open a terminal and run:

dotnet new webapi -n MyFirstApi
cd MyFirstApi

dotnet new webapi creates a new ASP.NET Core Web API project. The -n flag names it. You now have a folder with several files. Before you run anything, understand what you are looking at.

What Got Generated

Open the project in your editor. The files that matter:

Program.cs — The entry point. This is where the application is configured and started. In modern .NET, this file is short. It sets up services, configures the request pipeline, and maps routes.

MyFirstApi.csproj — The project file. It declares which .NET version you are targeting, your dependencies, and build settings. You rarely edit this by hand.

appsettings.json — Configuration. Connection strings, logging levels, feature flags — anything that might change between environments goes here. Right now it just has logging defaults. Leave it alone.

Properties/launchSettings.json — Defines how the app launches during development, including which ports to use. The default template sets up both HTTP and HTTPS URLs.

The template also includes a sample WeatherForecast endpoint. Delete that. You are going to build your own.

Two Approaches: Minimal APIs vs Controllers

ASP.NET Core gives you two ways to define endpoints. You need to know both exist, but you only need one to start.

Minimal APIs define routes directly in Program.cs:

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

Controller-based APIs use classes with attributes:

[ApiController]
[Route("api/[controller]")]
public class HelloController : ControllerBase
{
    [HttpGet]
    public string Get() => "Hello, world!";
}

Both produce the same HTTP response. Controllers add structure, conventions, and ceremony. Minimal APIs are direct and concise.

For learning, start with minimal APIs. The mapping between "I want an endpoint" and "here is the code" is immediate. There is no base class to inherit, no attributes to memorize, no folder conventions to follow. You write a route, you write a handler, it works. Controllers make sense later when your application grows and you need to organize dozens of endpoints into logical groups.

Build Your First Endpoints

Replace the contents of Program.cs with this:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

var app = builder.Build();

if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

var items = new List<Item>();
var nextId = 1;

app.MapGet("/api/items", () => items);

app.MapPost("/api/items", (ItemRequest request) =>
{
    var item = new Item(nextId++, request.Name, request.Description);
    items.Add(item);
    return Results.Created($"/api/items/{item.Id}", item);
});

app.Run();

record Item(int Id, string Name, string Description);
record ItemRequest(string Name, string Description);

That is a complete, working API. Run it:

dotnet run

The terminal will show the URL the app is listening on, something like http://localhost:5XXX. Open a browser and go to that URL with /swagger appended. You will see the Swagger UI — an interactive page where you can test your endpoints without writing any client code.

What Each Line Does

Walk through it:

WebApplication.CreateBuilder(args) — Creates the application builder. This is the setup phase where you register services, configure options, and prepare everything the app needs before it starts handling requests.

builder.Services.AddEndpointsApiExplorer() and builder.Services.AddSwaggerGen() — Register the services that generate API documentation. Swagger reads your endpoint definitions and creates an interactive test page. This is built into the template and costs you two lines.

app.UseSwagger() and app.UseSwaggerUI() — Add the Swagger middleware to the request pipeline, but only in development. You do not want API documentation exposed in production.

var items = new List<Item>() — An in-memory list acting as your data store. This is not how you build production software. It is how you learn without needing a database. Every time the app restarts, the data is gone. That is fine for now.

app.MapGet("/api/items", () => items) — When a GET request hits /api/items, return the full list. ASP.NET Core automatically serializes the list to JSON. You did not write any serialization code. The framework handles it.

app.MapPost("/api/items", (ItemRequest request) => { ... }) — When a POST request hits /api/items with a JSON body, ASP.NET Core deserializes the body into an ItemRequest object, your handler creates a new Item, adds it to the list, and returns a 201 Created response with the new item and its location.

Results.Created(...) — A helper that returns the correct HTTP status code (201) and sets the Location header. Using the right status codes is not pedantic. Clients depend on them.

record Item(int Id, string Name, string Description) — A C# record. Records are immutable data types with built-in equality and a concise syntax. Perfect for API models. One line instead of thirty lines of boilerplate class code.

Test It

With the app running, you have two options.

Swagger UI — Navigate to /swagger in your browser. Click on POST /api/items, hit "Try it out", and submit this body:

{
  "name": "Learn ASP.NET Core",
  "description": "Build a REST API from scratch"
}

Then hit GET /api/items to see your item in the list.

curl — From another terminal:

curl -X POST http://localhost:5000/api/items \
  -H "Content-Type: application/json" \
  -d '{"name": "Learn ASP.NET Core", "description": "Build a REST API from scratch"}'

curl http://localhost:5000/api/items

Adjust the port to match what your app is actually running on. Check the terminal output from dotnet run for the correct URL.

Add a Parameter Endpoint

Add these two endpoints before app.Run():

app.MapGet("/api/items/{id}", (int id) =>
{
    var item = items.FirstOrDefault(i => i.Id == id);
    return item is not null ? Results.Ok(item) : Results.NotFound();
});

app.MapDelete("/api/items/{id}", (int id) =>
{
    var item = items.FirstOrDefault(i => i.Id == id);
    if (item is null) return Results.NotFound();
    items.Remove(item);
    return Results.NoContent();
});

{id} in the route is a route parameter. ASP.NET Core extracts the value from the URL and passes it to your handler as the int id argument. The framework handles parsing the string from the URL into an integer.

FirstOrDefault is a LINQ method that searches the list and returns the first match, or null if nothing matches. The is not null check uses C# pattern matching to decide the response: return the item with a 200, or return a 404.

The DELETE endpoint follows the same pattern — find the item, return 404 if missing, remove it and return 204 No Content if found. No Content is the correct status code for a successful deletion that has no response body.

You now have a full CRUD-minus-update API: create, read, read by ID, and delete.

What Is Happening Under the Hood

Three things are working together every time a request arrives.

The Request Pipeline — When an HTTP request reaches your application, it passes through a series of middleware components. Each middleware can inspect, modify, or short-circuit the request. Swagger is middleware. HTTPS redirection is middleware. Your endpoints are the final stop. The order you register middleware in Program.cs is the order requests flow through them.

Routing — The routing system matches incoming URLs to your endpoint definitions. MapGet("/api/items/{id}", ...) registers a pattern. When a request for GET /api/items/3 arrives, the router matches it, extracts 3 as the id parameter, and invokes your handler. This matching is efficient — it is not checking every route sequentially.

Serialization — ASP.NET Core uses System.Text.Json by default to convert your C# objects to JSON on the way out and JSON request bodies to C# objects on the way in. When your handler returns an Item record, the framework serializes every public property to a JSON object. When a POST body arrives, the framework deserializes it into your ItemRequest record. You get this for free. If you need custom behavior — different property names, null handling, specific date formats — you configure the serializer, but the defaults work for most cases.

The Takeaway

You built a REST API with four endpoints in roughly 40 lines of C# code. No boilerplate classes, no XML configuration, no deployment ceremony. The dotnet new webapi template, minimal APIs, and built-in Swagger support make the path from "nothing" to "working endpoints" as short as it has ever been in .NET.

The core pattern repeats: register a route, write a handler, return a result. Everything else — routing, serialization, content negotiation, status codes — the framework handles. Your job is the logic.

This is an in-memory demo. Real applications need persistent storage, input validation, error handling, and authentication. Those are the next layers to add, and each one builds directly on what you wrote today.

Next in the learning path: The next post in the Getting Started with .NET series covers connecting your API to a real database with Entity Framework Core — replacing that in-memory list with persistent storage that survives restarts.

Comments


bottom of page