Building Minimal APIs In .NET 6

This post is part of a series on .NET 6 and C# 10 features. Use the following links to navigate to other articles in the series and build up your .NET 6/C# 10 knowledge! While the articles are seperated into .NET 6 and C# 10 changes, these days the lines are very blurred so don’t read too much into it.

.NET 6

Minimal API Framework
DateOnly and TimeOnly Types
LINQ OrDefault Enhancements
Implicit Using Statements
IEnumerable Chunk
SOCKS Proxy Support
Priority Queue

C# 10

Global Using Statements
File Scoped Namespaces

Around 6-ish years ago, NodeJS really hit it’s peak in popularity. In part, it was because people had no choice but to learn Javascript because of the popularity of front end JS frameworks at the time, so why not learn a backend that uses Javascript too? But also I think it was because of the simplicity of building API’s in NodeJS.

Remember at the time, you were dealing with .NET Framework’s bloated web template that generated things like an OWIN pipeline, or a global.asax file. You had things like MVC filters, middleware, and usually we were building huge multi tier monolithic applications.

I remember my first exposure to NodeJS was when a company I worked for was trying to build a microservice that could do currency conversions. The overhead of setting up a new .NET Framework API was overwhelming compared to the simplicity of a one file NodeJS application with a single endpoint. It was really a no brainer.

If you’ve followed David Fowler on Twitter at any point in the past couple of years, you’ve probably seen him mention several times that .NET developers have a tendency to not be able to create minimal API’s at all. It always has to be a dependency injected, 3 tier, SQL Server backed monolith. And in some ways, I actually agree with him. And that’s why, in .NET 6, we are getting the “minimal API” framework to allow developers to create micro APIs without the overhead of the entire .NET ecosystem weighing you down.

Getting Setup With .NET 6 Preview

At the time of writing, .NET 6 is in preview, and is not currently available in general release. That doesn’t mean it’s hard to set up, it just means that generally you’re not going to have it already installed on your machine if you haven’t already been playing with some of the latest fandangle features.

To get set up using .NET 6, you can go and read out guide here :

Remember, this feature is *only* available in .NET 6. Not .NET 5, not .NET Core 3.1, or any other version you can think of. 6.

Introducing The .NET 6 Minimal API Framework

In .NET 5, top level programs were introduced which essentially meant you could open a .cs file, write some code, and have it run without namespaces, classes, and all the cruft holding you back. .NET 6 minimal API’s just take that to another level.

With the .NET 6 preview SDK installed, open a command prompt in a folder and type :

dotnet new web -o MinApi

Alternatively, you can open an existing console application, delete everything in the program.cs, and edit your .csproj to look like the following :

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

If you used the command to create your project (And if not, just copy and paste the below), you should end up with a new minimal API that looks similar to the following :

using Microsoft.AspNetCore.Builder;

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

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


This is a fully fledged .NET API, with no DI, no configuration objects, and all in a single file. Does it mean that it has to stay that way? No! But it provides a much lighter weight starting point for any API that needs to just do one single thing.

Of course, you can add additional endpoints, add logic, return complex types (That will be converted to JSON as is standard). There really isn’t much more to say because the idea is that everything is simple and just works out of the box.

Adding Dependency Injection

Let’s add a small addition to our API. Let’s say that we want to offload some logic to a service, just to keep our API’s nice and clean. Even though this is a minimal API, we can create other files if we want to right?!

Let’s create a file called HelloService.cs and add the following :

public class HelloService
    public string SayHello(string name)
        return $"Hello {name}";

Next, we actually want to add a nuget package so we can have the nice DI helpers (Like AddSingleton, AddTransient) that we are used to. To do so, add the following package but ensure that the prerelease box is ticked as we need the .NET 6 version of the package, not the .NET 5 version.

Next, let’s head back to our minimal API file and make some changes so it ends up looking like so :

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddSingleton<HelloService>(new HelloService());

var app = builder.Build();

app.MapGet("/hello", (HttpContext context, HelloService helloService) => helloService.SayHello(context.Request.Query["name"].ToString()));


Here’s what we’ve done :

  • We added our HelloService as a dependency to our service collection (Much like we would with a full .NET API)
  • We modified our API endpoint to inject in our HttpContext and our HelloService
  • We used these to generate a response out, which should say “Hello {name}”. Nice!

We can obviously do similar things if we wish to load configuration. Again, you’re not limited by using the minimal API template, it’s simply just a way to give you an easier boilerplate for micro APIs that don’t come with a bunch of stuff that you don’t need.

Taking Things Further

It’s very early days yet, and as such, the actual layout and code required to build minimal API’s in .NET 6 is changing between preview releases. As such, be careful reading other tutorials out on the web on the subject, because they either become outdated very quickly *or* more often than not, they guess what the end API will look like, and not what is actually in the latest release. I saw this a lot when Records were introduced in C# 9, where people kinda “guessed” how records would work, and not how they actually did upon release.

So with that in mind, keep an eye on the preview release notes from Microsoft. The latest version is here : and it includes how to add Swagger to your minimal API. Taking things further, don’t get frustrated if some blog you read shares code, and it doesn’t work, just keep an eye on the release notes and try things as they come out and are available.

Early Adopter Bonus

Depending on when you try this out, you may run into the following errors :

Delegate 'RequestDelegate' does not take 0 arguments

This is because in earlier versions of the minimal framework, you had to cast your delegate to a function like so :

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

In the most recent preview version, you no longer have to do this, *but* the tooling has not caught up yet. So building and running via Visual Studio isn’t quite working. To run your application and get past this error, simply use a dotnet build/dotnet run command from a terminal and you should be up and running. This is actually a pretty common scenario where Visual Studio is slightly behind an SDK version, and is just what I like to call an “early adopter bonus”. If you want to play with the latest shiny new things, sometimes there’s a couple of hurdles getting there.

9 thoughts on “Building Minimal APIs In .NET 6”

  1. Hello!
    The things described here is not new for .net apps, but only copy-paste from existing open-source projects I can see. I’ll providing few links to check it out down below. Could be usefull for someone who read that.

    5 min to configure:
    1. *EasyData* – lightweight CRUD operations.
    2. *Nancy* – cool project with easy setup API methods for small things to use quickly.

    • Totally agree. We even have a guide to use NancyFX on here!

      The idea however is that someone new to .NET Core has an easier entry point to getting an API up and running (Without having to add additional third party packages).

  2. Nice to see it. However can I still create Program class with Main method which can hold all these lines of code: creating builder and so on?

    • Yes you can actually! You don’t have to use Top level programs at all and can use the standard console application with namespaces, main method etc.

    • The ‘dotnet new’ templates in .Net 6 default to top level statement and implicit usings.
      If you don’t want to use these features, the advice given (in the articles I’ve read) is to use (for example in a console) “dotnet new console –framework net5.0”, and then edit the .net version in the project file to 6.
      What I managed to do is edit the template package ‘microsoft.dotnet.common.projecttemplates.’ in ‘/usr/share/dotnet/templates/6.0.1’ (this example is on Linux). I made a copy, then (for the example of a Console app) I extracted and edited the contents of ‘template.json’ at ‘content/ConsoleApplication-CSharp/.template.config’ to find the two features mentioned, and change their value to ‘false’. I saved the file and then copied the ‘contents’ folder back into the .nupkg archive. Then I copied the modified .nupkg file back to the location where I found it.
      The next time I did ‘dotnet new console’, I got what I wanted, which was the .Net 6 project generated with the old style.

  3. This intro is very useful – thank you, and keep up this good work …
    At first sight, I find omitting “main” quite confusing. Generated MSIL contains “main” anyway, so I would prefer to have an option to keep it in the initial code. Concerning this, the suggestion of Luddite is interesting.


Leave a Comment