Introduction to .NET Minimal APIs

Athan Bonis
8 min readMay 23, 2023

--

Have you ever wondered why you still need all this boilerplate code with Controllers in your .NET Web API since you don’t implement a Web Application?

If you ever thought about it —.NET Minimal APIs (that had already been released back in November 2021 with .NET 6) may be a good fit for you.

But before going into details, let’s see what this thing is. I hate long and boring descriptions, so I will just provide you with some bullets that ticked the boxes in my mind when I first read about them:

  • Simplifies Web API development by reducing code complexity
  • Provides a streamlined syntax for defining API endpoints with less boilerplate
  • Embraces a minimalist approach, focusing on the essentials of API development
  • Enhances developer productivity by eliminating unnecessary complexity

I can continue providing bullets of advantages — although there are still some missing features, but I think already provided some valid points and we can start looking at how to setup it up.

For the sake of this tutorial, we will create a simple Web API for managing Orders.

So.. Let’s go!

Step 1 — Create and Run the Web API project

Open your terminal and create a new .NET project:

dotnet new web -n OrderService

The above command will create for you an empty .NET Core Web API using the default template.

Note: In this tutorial, I am using .NET7, so keep that in mind, if you needed to adapt the versions of the packages that you will need to add to the project.

Locate the project and open it with a Code Editor like VS Code or any IDE that supports .NET projects and let’s see what we have inside:

Super minimal, right?

Ok, what’s the structure of the csproj file?

<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
</Project>

We can see a very basic setup:

  • Targeting .NET7.0 version
  • Nullable attribute enabled — which is used to enable the nullable reference types feature in C#. When enabled it activates nullable reference types for the entire project. You can find a ton of information about it here: Nullable reference types | Microsoft Learn
  • Implicit Usings — Tim Corey explains in depth this concept in his 10 minutes or less video series.

Now, take a look inside the Program.cs:

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

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

app.Run();

With just 4 lines of code, we can have an up-and-running Web API that we can take as a baseline to start building our own API.

Let’s run it to confirm that everything seems ok until this step:

dotnet run

As the console says, we are up and running:

Building...
info: Microsoft.Hosting.Lifetime[14]
Now listening on: http://localhost:5006
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: C:\repos\OrderService

So, hit it!

We have a response! I hope you didn’t get bored, did you? We just started, hold on.

Step 2 — Add OpenAPI support

While we could easily have this step inside the previous one if we would use Visual Studio or any other .NET IDE since they provide direct support for Swagger when creating a new Web API project, I prefer to start explaining things with the bare minimum code.

If you already created the project via IDE and you already added the OpenAPI support feel free to skip this step.

Add the required package, which is the Swashbuckle.AspNetCore:

dotnet add package Swashbuckle.AspNetCore --version 6.5.0

Let’s see if the package was added correctly in the .csproj file:

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

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

<ItemGroup>
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.5.0" />
</ItemGroup>

</Project>

Ok, so far so good. Now we need to add a little bit of code to configure it. Before building the WebApplication instance we need to register the appropriate services, so we will end up with this piece of code:

var builder = WebApplication.CreateBuilder(args);

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

var app = builder.Build();

Let’s not dive deeper into what these two added lines register behind the scenes in order to keep this tutorial short, but briefly:

  • builder.Services.AddEndpointsApiExplorer(): This line registers the services required for API exploration and metadata generation.
    builder.Services.AddSwaggerGen(): This line registers the services required for generating OpenAPI documentation.

Now that we registered the appropriate services, we need something more in order to make it work, right? Just registration of the services will not do anything by themselves.

After building the WebApplication instance and before Running the app add these lines of code in order to add the OpenAPI middleware:

app.UseSwagger();
app.UseSwaggerUI();

And now finally we are done! Your code should look like this now:

var builder = WebApplication.CreateBuilder(args);

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

var app = builder.Build();

app.UseSwagger();
app.UseSwaggerUI();

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

app.Run();

Run it again, open your preferred browser, and hit your server with /swagger (ex. http://localhost:5006/swagger ):

Viola! We have also Swagger now:

Step 3 — Create Endpoint to Add an Order

First, we should have a structure for our Order, so we will create a record for the sake of this tutorial, just to keep it as simple as possible:

public record Order(int Id, int ProductId, int Quantity, int CustomerId, DateTime Created);

Simple as that, we will not add more things for now. If we wish, we can add more things, like comments, details etc.

Let’s remove now the Hello World endpoint — or just change it:

app.MapPost("/add", (int productId, int quantity, int customerId) => {
var random = new Random();
return new Order(random.Next(), productId, quantity, customerId, DateTime.UtcNow);
});

The endpoint now changed to be a POST, it accepts three parameters, productId, and quantity

As you can imagine, this will create a new Order, with a random Id, and will be marked with the UtcNow Date and Time, and indeed:

It’s evolving, right? Slowly, but it’s evolving.

We can also define the customerIdto come from Headers:

app.MapPost("/add", (int productId, int quantity, [FromHeader] int customerId) => {
var random = new Random();
return new Order(random.Next(), productId, quantity, customerId, DateTime.UtcNow);
});

Step 4 — Inject a Service

We will now add an in-memory database using EF Core in order to show a more realistic scenario and also show how to inject a Service as well.

Install the Microsoft.EntityFrameworkCore.InMemory package:

dotnet add package Microsoft.EntityFrameworkCore.InMemory --version 7.0.5

I will create now a new file Order.cs to add there the OrderDbContext and I will move the Order record there as well to have them all together there:

using Microsoft.EntityFrameworkCore;
namespace OrderService;

public class OrderDbContext : DbContext
{
protected override void OnConfiguring
(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseInMemoryDatabase(databaseName: "OrderDb");
}

public DbSet<Order> Orders { get; set; }
}

public record Order(int Id, int ProductId, int Quantity, int CustomerId, DateTime Created);

No magic here, we just configure the DbContext to use an in-memory database and define a DbSet for the Orders.

So, since we created our DbContext now we need to register it in our Dependency Injection container:

builder.Services.AddDbContext<OrderDbContext>();

Ok. We created our DbContext , we registered it, and now? How do we inject it into our endpoint methods?

We just add to the /add endpoint our OrderDbContext and we are ready to use it:

app.MapPost("/add", (
int productId,
int quantity,
[FromHeader] int customerId,
OrderDbContext dbContext) => {

var random = new Random();
return new Order(random.Next(), productId, quantity, customerId, DateTime.UtcNow);
});

We defined our DbContext, we registered it and injected it into the endpoint that we want to use it. Let’s just add now some code there to save the incoming order to our database:

app.MapPost("/add", async (
int productId,
int quantity,
[FromHeader]
int customerId,
OrderDbContext dbContext) => {

var random = new Random();
var order = new Order(random.Next(), productId, quantity, customerId, DateTime.UtcNow);

dbContext.Orders.Add(order);
await dbContext.SaveChangesAsync();

return order;
});

Let’s test out our work so far, run it again, and try your /add method:

It seems that worked! It would be nice to have also an endpoint to get these orders, right? Let’s do it:

app.MapGet("/", (OrderDbContext dbContext) => 
dbContext.Orders.ToArrayAsync());

We created a GET endpoint that goes into our database and gives us the results. Run it again, add some orders with your /add endpoint, and then test the GET endpoint:

That’s it! The orders now are being saved into the database, and you also can get them from there. We maybe want to add an optional filter for productId :

app.MapGet("/",  (int? productId, OrderDbContext dbContext) =>
{
return productId.HasValue
? dbContext.Orders.Where(x => x.ProductId == productId.Value).ToArrayAsync()
: dbContext.Orders.ToArrayAsync();
});

So we will filter the results based on the provided productId :

Pretty simple, right?

If you reached this point of the article, I really thank you for spending your minutes with me learning the very basics around the .NET Minimal APIs.

In the next articles about them, we will discuss several topics such as MediatR, Authentication and Authorization using JWT Token, Exception Handling, and several other things. Stay tuned!

You can find the source code of this tutorial in my GitHub profile AthanCB (Athan Bonis) (github.com) by following this link:

AthanCB/minimal-apis-examples (github.com)

I have also created branches related to the Steps that I described during this tutorial, in case you want to see them step by step.

We will talk through the articles in the next one!

For more articles consider making a follow on my account.

--

--

Athan Bonis

Ηighly motivated Software Engineer. I have a strong passion for problem-solving and learning new technologies, targeting to produce clean and structured code.