Blog
Overview of Minimal APIs with .NET 6 and C# 10
What are Minimal APIs?
An unopiniatonated way to build high performance APIs in .NET with the minimal amount of implicitly added features and the maximal amount of flexibility to explicitly opt in the features and the structure that you need
- Nick Chapsas, NDC London 2022
The Minimal APIs journey starts with the will of the .NET development team to allow developers to embrace minimalism. We have been talking a lot for years now about how to avoid massive monolithic applications and to prefer microservices, but .NET never offered a way to really create minimal APIs. The reason is that APIs are based on the MVC layer, so they inherit a lot of useless code which makes them more complex and resource-consuming than they should be. Minimal APIs are the .NET team's answer to all this useless complexity. Now .NET, like the majority of the other development languages, offers a way to create really minimal APIs with just what you need inside. In that respect, Minimal APIs are a major .NET feature.
Minimal APIs are not a new concept
Even if they didn't exist in .NET, minimal APIs have existed for years now in other development environments.
Node.JS
const express = require('express')
const app = express()
const port = 3000
app.get('hello world', (req, res) => { req.send('Hello World!'); })
app.listen(port)
GO - GIN
import {
"github.com/gin-gonic/gin",
"net/http"
}
func main() {
r := gin.default()
r.GET("/", func(c *gin.Context){
c.String(http.StatusOK, "Hello World!")
})
r.Run()
}
Python - FastAPI
from fastapi import FastAPI
app = FastAPI()
@app.get('/')
def read_root():
return { "hello": "Hello World!" }
To be fair, it is not exactly right to say that Minimal APIs didn't exist in .NET before .NET 6 and C# 10: they did exist, but you needed to use a package named NancyFX, a lightweight and low-ceremony framework for building HTTP-based services on .NET and Mono, and it was pretty confidential:
.NET - NancyFX
public class SampleModule : Nancy.NancyModule
{
public SampleModule()
{
Get["/"] = _ => "Hello World!";
}
}
Although NancyFX is still available, notice that this project has been archived on GitHub since .NET Core and is no longer maintained: ** Announcement ** - Nancy is no longer being maintained!.

These code samples are just a few examples of Minimal APIs in other development environments, but they exist in pretty much all languages; .NET and C# are among the last to incorporate them with official support.
How can they work?
The Minimal APIs are based on several C# 9 and C# 10 features, mainly C# 10 features:
- Top-level statements - C# 9 feature
- Implicit using statements - C# 10 feature
- Global using statements - C# 10 feature
- Inferred lambda types - C# 10 feature
- Attributes on lambdas - C# 10 feature
Let's dig a little into these features.
Top-level statements
Top-level statements enable you to avoid the extra ceremony required by placing your program's entry point in a static method in a class.
Before C# 9, a minimal console application was written with 11 lines:
using System;
namespace Application
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Hello World!");
}
}
}
Since Top-level statements and Implicit and Global using statements, you can write a minimal console application with only one line, the only one that actually does something:
Console.WriteLine("Hello, World!");
Global using statements
Global using statements, which came with .NET 6 and C# 10, are the possibility to place all the common using statements in a single file, which makes them available for use within the entire project, so you won't have to put them at the top of each file anymore.
global using System;
global using System.Collections.Generic;
global using System.IO;
global using System.Linq;
Implicit using statements
.NET 6 with C# 10 introduced support for implicit namespaces in C# projects, which already existed for Visual Basic projects, by the way.
Implicit usings are the possibility for .NET to bring used namespaces into the default ones so you don't have to explicitly declare them. Behind the scenes, .NET stores them as global usings in a hidden auto-generated file in your obj folder.
global using global::System;
global using global::System.Collections.Generic;
global using global::System.IO;
global using global::System.Linq;
Inferred lambda types
The Inferred lambda types feature allows you to use var or an inline return type in lambdas. Prior to C# 10 you had to explicitly define the delegate type, such as Func
Func func = (string firstName, string lastName) => $"Hi! I am {firstName} {lastName}";
Action writeName = (string firstName, string lastName) =>
{
Console.WriteLine($"Hi! I am {firstName} {lastName}");
};
writeName("Adrien", "Torris");
Console.ReadLine();
It was a bit frustrating to write all this code but since C# 10 the type can be inferred by the compiler. If the compiler cannot determine the type to use, it will throw an error so that you can define it explicitly.
So our Func line in C# 9 becomes, with C# 10:
var func = (string firstName, string lastName) => $"Hi! I am {firstName} {lastName}";
Attributes on lambdas
Beginning with C# 10, you can add attributes to a lambda expression and its parameters. Here is an example with an obsolete tag:
var func = [Obsolete](string firstName, string lastName) => $"Hi! I am {firstName} {lastName}";
You can also add attributes to the input parameters or return value, as the following example shows:
var sum = ([Example(1)] int a, [Example(2), Example(3)] int b) => a + b;
var inc = [return: Example(1)] (int s) => s++;
What are Minimal APIs?
A minimal API has 2 fundamental components:
- the web application builder, where to put all the configuration of the application
- the web application itself, where to configure all the middleware
// Application builder which can access the arguments passed to the application
var builder = WebApplication.CreateBuilder(args);
// Materialization of the configuration builder
var app = builder.Build();
// Start the web application.
app.Run();
At this point, the application is totally valid and works, but it has no endpoint so it's pretty useless for now.
Before we add some behaviors to this API, notice that you can treat the lines between our first two as the ConfigureServices method of the startup.cs file in our old/current way of doing web APIs, and the lines between our last two as the Configure method of the startup file.
var builder = WebApplication.CreateBuilder(args);
// ConfigureServices
var app = builder.Build();
// Configure
app.Run();
Let's add some endpoints now:
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("hello", () => "Hello You! How are you?");
app.Run();
I used strings for simplicity here but you can use objects if you want to:
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("hello2", () => new
{
Message = "Hello You! How are you?"
});
app.Run();
We can also use the Results class, which handles some result behaviors:
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("user", () => Results.Ok(new User("Adrien Torris"));
app.Run();
record User(string fullName);
We can also use the HTTP context or the HTTP request objects, and use the HTTP response object to customize our response:
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("hello", async (HttpRequest request, HttpResponse response) =>
{
await response.WriteAsJsonAsync(request.Query);
});
app.Run();
We made some GET examples but you have a Map method for several HTTP verbs like POST, PUT or DELETE.
The minimal API is capable of dealing with route parameters the same way as controllers:
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapPost("user/{id:int}", (int id) =>
{
id
});
app.Run();
You can use the routing API the same way as with controllers:
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapPost("user/{id:int}", ([FromQuery]int id, [FromHeader]string accept, [FromBody]User user) =>
{
id, accept, user
});
app.Run();
record User(string fullName);
To see more of the possibilities offered by Minimal APIs you should take the time to browse the Dodyg samples about them, they are just a goldmine (like all his samples): Minimal APIs samples.
What do Minimal APIs not support?
- No support for filters (it seems that an equivalent will be coming pretty soon but it won't be exactly the same thing as today, so we probably won't be able to use the filters we have in our current MVC projects).
- No built-in support for validation
- No support for JsonPatch
- No support for OData
- No support for API versioning
All the Minimal APIs stuff may seem weird at first because it's a real breaking change compared to the way we developed APIs before .NET 6, but when you think about it, isn't it the natural way to do it? After all, all the major environments have adopted this approach for years, and .NET didn't do it sooner for simplicity, because the functionality had already been developed in a stack so it was simpler to just use it. But are Controllers and all the MVC stuff really great for APIs? Are APIs that close to MVC websites?
Why is the Controllers approach weird for APIs?
- Controllers come from MVC, for which they were designed, but APIs do not need all the MVC stuff
- They have methods that never call one another, and that's fine because generally their purposes are totally different, so why always put them in the same object?
- They violate the Single Responsibility principle (depending on your definition of "Single")
- They can have services injected that aren't used by all Actions, which is a waste of resources
- They never share state between Actions, so what is the whole point of having them together?
- Most of the time even the private methods are used by only one Action
- They invoke Middlewares and Filters you do not need; they invoke them because all this stuff comes from MVC (like the validation for example)
- They can grow a lot leading to "fat controllers"
Can Minimal APIs have some structure?
The complaint I hear the most about Minimal APIs is that they have no structure, but this complaint just doesn't make any sense! Minimal APIs are not "Single File APIs", they are Minimal APIs. If you develop an MVC-way API and put all your code in a single file, it doesn't make the MVC way bad, it's just you who isn't doing things right. It's the same for Minimal APIs: they can be developed in a single file, which will most of the time be a bad practice, and they can be structured like any .NET project in an elegant and coherent structure. I invite you to consult the clean minimal API sample by Nick Chapsas, which is a structured Minimal API project:

In this sample, Nick Chapsas has created separate endpoint classes to avoid useless coupling between them.
This is an example for the endpoint to get an item, a consumer here:
using Customers.Api.Contracts.Requests;
using Customers.Api.Contracts.Responses;
using Customers.Api.Mapping;
using Customers.Api.Services;
using FastEndpoints;
using Microsoft.AspNetCore.Authorization;
namespace Customers.Api.Endpoints;
[HttpGet("customers/{id:guid}"), AllowAnonymous]
public class GetCustomerEndpoint : Endpoint
{
private readonly ICustomerService _customerService;
public GetCustomerEndpoint(ICustomerService customerService)
{
_customerService = customerService;
}
public override async Task HandleAsync(GetCustomerRequest req, CancellationToken ct)
{
var customer = await _customerService.GetAsync(req.Id);
if (customer is null)
{
await SendNotFoundAsync(ct);
return;
}
var customerResponse = customer.ToCustomerResponse();
await SendOkAsync(customerResponse, ct);
}
}
As you can see, this project is perfectly structured but has the minimal amount of built-in code, which is the whole purpose of Minimal APIs.
To conclude
When you think about it, the minimalism and low-ceremony approach seems to be the better one, the "natural" one. Calling it "Minimal APIs" is maybe a mistake because it implies that it's not the default way to do it, which can be a little scary, when the old way is probably the wrong one. Maybe Microsoft should have named "Minimal APIs" just "APIs" and the MVC way of doing it "Unessential APIs" or "Massive APIs".
In any case, .NET 6 brings us an elegant and efficient way of developing APIs that I invite you to discover and try if you haven't already done so. The first step (or the second one if you are reading these lines...) should be to watch the brilliant talk by Nick Chapsas at NDC London.