intro-to-dotnet-web-dev/4-minimal-api
2025-05-12 22:44:16 -07:00
..
0-start/PizzaStore Update launchSettings.json, Program.cs, and README.md for .NET 9 compatibility and enhanced Swagger integration 2025-05-12 22:44:16 -07:00
1-complete/PizzaStore Update launchSettings.json, Program.cs, and README.md for .NET 9 compatibility and enhanced Swagger integration 2025-05-12 22:44:16 -07:00
README.md Update launchSettings.json, Program.cs, and README.md for .NET 9 compatibility and enhanced Swagger integration 2025-05-12 22:44:16 -07:00
swagger-crud.png Add Minimal APIs content 2022-06-24 16:14:43 -07:00
swagger.png Add Minimal APIs content 2022-06-24 16:14:43 -07:00

Build an HTTP backend with Minimal APIs

What is an HTTP API?

Web servers are often used to serve web pages, but they can also be used to serve other types of data. For example, a web server can be used to provide data for other clients, such as a mobile app, a desktop app, another web server. Sometimes, we'll use front-end single page applications (SPAs) like Angular or React in the browser, but they'll interact with data on the server behind the scenes. Services like these are "Application Programming Interfaces" (APIs), rather than user interfaces like web browsers.

HTTP: the instruction set for web APIs

Suppose the server has a list of pizzas that you want to interact with from your client program. There are some really common things youll want to do with a list of data (pizzas in this case) as a programmer:

  • Get a list of all the pizzas
  • Get the details about one pizza
  • Add a new pizza to the list
  • Update the details for a pizza
  • Remove a pizza from the list

Because these things are so common, the web was designed with standard commands to handle them: GET, PUT, POST, and DELETE. These are part of the HTTP (short for HyperText Transport Protocol) specification, so anyone building a browser or server knows what to expect. Programmers build APIs for all kinds of things, from operating systems to smart watches to refrigerators, so we often talk about web focused APIs as HTTP APIs.

Serialization data with JSON

To avoid reinventing things over and over, we use some common standards for HTTP APIs. Weve already talked about HTTP, which handles the commands and communication between clients and servers. Another important thing for HTTP APIs to do in a predictable way is to send data back and forth. The most common way to package up data now is called JSON, short for JavaScript Object Notation. Its a neat, simple format for packaging up data, but programmers dont want to spend time converting their information back and forth between properties and classes in their programming language and JSON. This conversion process is called serialization, and fortunately ASP.NET Core can do that for you automatically.

Welcome to the internet

Another important thing to think about for server exposed to the public internet is security and authorization. Some HTTP APIs require authentication, so only clients with the right credentials can access them. Even public HTTP APIs need to handle security, to manage things like denial of service attacks and exploiting the public APIs to take over the server or get access to information they shouldnt have. Fortunately, ASP.NET Core can handle things like this for us, too.

Keep it simple with Minimal APIs

By now, you might be thinking that building an API that speaks HTTP, serializes JSON, manages security, etc., is going to be really hard! Fortunately, ASP.NET Core has a really easy way to get started, called Minimal APIs. Believe it or not, this is all you need to build a basic Minimal API:

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/", () => "Hello World!");
app.Run();

The first two lines set up the server, the third line returns some information (in this case “Hello World!”), and the fourth line runs it. That third line is called an endpoint, and it just says that whenever a client sends a GET command to our server, it will run some code. We have similar commands for all the other HTTP verbs, so creating a new Pizza on the server would look like this:

app.MapPost("/pizzas", (Pizza pizza) => PizzaDB.CreatePizza(pizza));

This endpoint accepts a pizza JSON object, turns it into C#, and passes it to a PizzaDB class to save it to a database.

Building a Minimal API

Create a new Minimal API project

First, you need to scaffold a project. You've installed .NET 9 and you're ready to go.

  1. Create a web API by running dotnet new:

    dotnet new web -o PizzaStore -f net9.0
    

    You should see the PizzaStore directory.

  2. Run the app by calling dotnet run. It builds the app and hosts it on a port from 5000 to 5300. HTTPS has a port selected for it in the range of 7000 to 7300.

    cd PizzaStore
    dotnet run
    

    Here's what the output can look like in the terminal:

    Building...
     info: Microsoft.Hosting.Lifetime[14]
           Now listening on: https://localhost:7200
     info: Microsoft.Hosting.Lifetime[14]
           Now listening on: http://localhost:5100
     info: Microsoft.Hosting.Lifetime[0]
           Application started. Press Ctrl+C to shut down.
     info: Microsoft.Hosting.Lifetime[0]
           Hosting environment: Development
     info: Microsoft.Hosting.Lifetime[0]
           Content root path: /<path>/PizzaStore
    
  3. In your browser, go to the indicated port. According to the terminal http://localhost:{PORT}, you should see the text "Hello World!"

Congratulations! You've created an API by using a minimal API template.

Add automatic API documentation with Swagger

Use Swagger to ensure that you have a self-documenting API, where the docs change when you change the code. This also builds a really convenient web interface for your API, so you can test out the application as you build it.

  1. First, add the required Swagger packages to your project:

    cd PizzaStore
    dotnet add package Swashbuckle.AspNetCore
    dotnet add package Microsoft.AspNetCore.OpenApi
    
  2. Now update your Program.cs file with the following code:

    using Microsoft.OpenApi.Models;
    
    var builder = WebApplication.CreateBuilder(args);
    
    // Add services to the container
    // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
    builder.Services.AddEndpointsApiExplorer();
    builder.Services.AddSwaggerGen(options =>
    {
        options.SwaggerDoc("v1", new OpenApiInfo
        {
            Title = "PizzaStore API",
            Description = "Making the Pizzas you love",
            Version = "v1"
        });
    });
    
    var app = builder.Build();
    
    // Configure the HTTP request pipeline
    if (app.Environment.IsDevelopment())
    {
        app.UseSwagger();
        app.UseSwaggerUI();
    }
    
    app.MapGet("/", () => "Hello World!");
    
    app.Run();
    

    This code:

    • Adds the AddEndpointsApiExplorer service which is required for Swagger to discover and generate documentation for your API endpoints

    • Adds the AddSwaggerGen service to generate the OpenAPI specification for your API

    • Configures Swagger UI which provides an interactive UI for testing your API endpoints

    • Adds the AddEndpointsApiExplorer service which is required for Swagger to discover and generate documentation for your API endpoints

    • Adds the AddSwaggerGen service to generate the OpenAPI specification for your API

    • Configures Swagger UI which provides an interactive UI for testing your API endpoints

  3. Rerun the project and go to the app's address, http://localhost:{PORT}/swagger.

    You should see the following output:

    Swagger

Add a Pizza model and service

First you need some data. To store and manage data, you'll use an in-memory store. For this example, we're just going to use a simple list of pizzas.

  1. Create the file Db.cs in your project directory and give it the following content:

     using System.Collections.Generic;
     using System.Linq;
    
     namespace PizzaStore.DB; 
    
     public record Pizza 
     {
       public int Id { get; set; } 
       public string? Name { get; set; }
     }
    
     public static class PizzaDB
     {
       private static List<Pizza> _pizzas = new List<Pizza>()
       {
         new Pizza{ Id=1, Name="Cheese" },
         new Pizza{ Id=2, Name="Pepperoni" },
         new Pizza{ Id=3, Name="Pineapple extravaganza"} 
       };
    
       public static List<Pizza> GetPizzas() 
       {
         return _pizzas;
       } 
    
       public static Pizza? GetPizza(int id) 
       {
         return _pizzas.SingleOrDefault(pizza => pizza.Id == id);
       } 
    
       public static Pizza CreatePizza(Pizza pizza) 
       {
         _pizzas.Add(pizza);
         return pizza;
       }
    
       public static Pizza UpdatePizza(Pizza update) 
       {
         _pizzas = _pizzas.Select(pizza =>
         {
           if (pizza.Id == update.Id)
           {
             pizza.Name = update.Name;
           }
           return pizza;
         }).ToList();
         return update;
       }
    
       public static void RemovePizza(int id)
       {
         _pizzas = _pizzas.FindAll(pizza => pizza.Id != id).ToList();
       }
     }
    

Now that you have your data store, let's have the API use it next.

Connect data to routes

To connect your in-memory store to the API:

  1. Add the namespace. This addition is as simple as adding the proper using statement.
  2. Set up the routes. Make sure you add all the route mappings that are needed to create, read, update, and delete.
  3. Invoke it in the routes. Finally, you need to call the in-memory store per each route and pass in any parameters or body from the request, if applicable.

Now, connect data in your API.

  1. At the top of the Program.cs file, add the following line of code alongside the existing using statement:

    using Microsoft.OpenApi.Models;
    using PizzaStore.DB;
    
  2. Just before app.Run(), add the following code:

    // Define API endpoints with OpenAPI descriptions
    var pizzas = app.MapGroup("/pizzas")
        .WithTags("Pizzas")
        .WithOpenApi();
    
    // Get all pizzas
    pizzas.MapGet("/", () => PizzaDB.GetPizzas())
          .WithName("GetAllPizzas")
          .WithSummary("Get all pizzas")
          .WithDescription("Retrieves the complete list of available pizzas");
    
    // Get pizza by ID
    pizzas.MapGet("/{id}", (int id) => PizzaDB.GetPizza(id))
          .WithName("GetPizzaById")
          .WithSummary("Get pizza by ID")
          .WithDescription("Gets a specific pizza by its unique identifier")
          .WithOpenApi(operation => {
              operation.Parameters[0].Description = "The unique identifier for the pizza";
              return operation;
          });
    
    // Create a new pizza
    pizzas.MapPost("/", (Pizza pizza) => PizzaDB.CreatePizza(pizza))
          .WithName("CreatePizza")
          .WithSummary("Create a new pizza")
          .WithDescription("Adds a new pizza to the menu");
    
    // Update a pizza
    pizzas.MapPut("/", (Pizza pizza) => PizzaDB.UpdatePizza(pizza))
          .WithName("UpdatePizza")
          .WithSummary("Update an existing pizza")
          .WithDescription("Updates the details of an existing pizza");
    
    // Delete a pizza
    pizzas.MapDelete("/{id}", (int id) => PizzaDB.RemovePizza(id))
          .WithName("DeletePizza")
          .WithSummary("Delete a pizza")
          .WithDescription("Removes a pizza from the menu")
          .WithOpenApi(operation => {
              operation.Parameters[0].Description = "The unique identifier for the pizza to delete";
              return operation;
          });
    

    This is the actual API part of the application! In .NET 9, we're improving the OpenAPI documentation by:

    • Using .WithTags() to organize endpoints in the Swagger UI
    • Adding .WithSummary() and .WithDescription() to provide clear documentation
    • Using the advanced .WithOpenApi() overload to customize parameter descriptions
    • Organizing routes with MapGroup() for cleaner code
  3. Run the app by using dotnet run:

    dotnet run
    
  4. In your browser, go to http://localhost:{PORT}/swagger. You should see the following page rendering:

    Swagger

    What's great about this Swagger UI is that you can:

    • Browse all available endpoints organized by tags
    • See detailed documentation including summaries and descriptions
    • Expand any endpoint to see request parameters and response types
    • Try out the API directly with the "Try it out" button
    • Execute requests and see the actual responses without leaving the browser

What's next?

This is a quick first look at building a backend with Minimal APIs in .NET 9. To learn more about the latest features in Minimal APIs and OpenAPI support, check out the .NET 9 minimal API documentation and OpenAPI documents in ASP.NET Core.

In the next lesson, you'll learn about building a game with Blazor! Stay tuned!

Connect with us

We're excited to support you on your learning journey! Check out the .NET Community Page to find links to our blogs, YouTube, Twitter, and more.

How'd it go?

Please take this quick, 10 question survey to give us your thoughts on this lesson & challenge!