To keep Minimal APIs maintainable, group endpoints by feature instead of defining them all in one file.
You can write an extension method on IEndpointRouteBuilder for each feature, or use a library like Carter and its module concept.
Either way, related endpoints stay together as the API grows in complexity.
Keeping your business logic and data in-sync doesn't have to be a drag. Database configuration-as-code and auto-indexes mean your logic is always querying against what you expect after every release. Take off with RavenDB today and fly faster tomorrow.
In this week's newsletter we are going to explore Minimal APIs, which were introduced in .NET 6.
Minimal APIs were introduced to remove some of the ceremony
of creating traditional APIs with controllers.
To define an endpoint, you can use the new extension
methods, such as MapGet to define a GET endpoint.
I see one big issue with Minimal APIs, and that is the lack of clear guidance around how to structure applications built with Minimal APIs.
In this newsletter, I want to offer a few solutions for that problem.
Let's dive in.
How To Create Minimal APIs?
Let's define a simple Minimal API application with two endpoints.
We're going to create one GET endpoint for getting a list of products.
And one POST endpoint for saving a product to the database.
We're using the powerful DI feature that allows us to inject services
as expression arguments, which you can see in the two expressions below
where we are injecting the AppDbContext.
var builder = WebApplication.CreateBuilder(args);
// Configure EF and other services...
var app = builder.Build();
app.MapGet("/products", async (AppDbContext dbContext) =>
{
return Results.Ok(await dbContext.Products.ToListAsync());
});
app.MapPost("/products", async (Product product, AppDbContext dbContext) =>
{
dbContext.Products.Add(product);
await dbContext.SaveChangesAsync();
return Results.Ok(product);
});
app.Run();
And with this we have a functioning Minimal API that we can develop further as we continue to add more endpoints.
The Problem With Maintaining Minimal APIs
There is one potential problem with structuring our Minimal APIs like in the previous example. If we keep adding the Minimal API endpoints in the same file, our API will become hard to maintain as it grows in complexity. How can we solve the maintance problem with Minimal APIs?
One solution can be to use extension methods to encapsulate the definiton of the Minimal API endpoints.
Here's an example of that:
public static class ProductsModule
{
public static void RegisterProductsEndpoints(this IEndpointRouteBuilder endpoints)
{
endpoints.MapGet("/products", async (AppDbContext dbContext) =>
{
return Results.Ok(await dbContext.Products.ToListAsync());
});
endpoints.MapPost("/products", async (Product product, AppDbContext dbContext) =>
{
dbContext.Products.Add(product);
await dbContext.SaveChangesAsync();
return Results.Ok(product);
});
}
}
And then inside of Program we need to register the endpoints:
app.RegisterProductsEndpoints();
You can see that this simplifies our Minimal API definition,
and we also have our endpoints grouped by feature in one place.
I think this improve the maintainability of Minimal APIs,
but it comes at a cost. And that cost is having to define
extension methods for each group of endpoints you want to encapsulate,
and then you have to remember to call that extensions method in Program.
Can we do better?
Structuring Minimal API Projects With Modules
I want to introduce you to an interesting open source library called Carter, which has a concept of modules that we can use to group endpoints.
Here's how we can define our ProductsModule with Carter:
public class ProductsModule : ICarterModule
{
public void AddRoutes(IEndpointRouteBuilder app)
{
app.MapGet("/products", async (AppDbContext dbContext) =>
{
return Results.Ok(await dbContext.Products.ToListAsync());
});
app.MapPost("/products", async (Product product, AppDbContext dbContext) =>
{
dbContext.Products.Add(product);
await dbContext.SaveChangesAsync();
return Results.Ok(product);
});
}
}
This takes care of configuring our Minimal API endpoints, but we still
need to tell the framework to use these endpoints. We have to slightly
modify the Program to register the required Carter services by
calling AddCarter, and also map our endpoints by calling MapCarter.
var builder = WebApplication.CreateBuilder(args);
// Configure EF and other services...
builder.Services.AddCarter();
var app = builder.Build();
app.MapCarter();
app.Run();
When we want to define additional Minimal API endpoints we just need to
implement a new ICarterModule, and register our endpoints. Carter will
automatically take care of registering the new endpoints after that.
Would I Use Minimal APIs In a Real Project?
I think Minimal APIs have evolved nicely since they were first introduced in .NET 6. I would be careful with using them in very large applications, but I'm definitely going to explore options for using them on smaller projects.
A good use case can be for building a microservice that has a limited number of endpoints.
Frequently Asked Questions
What are Minimal APIs in .NET?
Minimal APIs, introduced in .NET 6, remove some of the ceremony of creating traditional APIs with controllers. You define endpoints with extension methods such as MapGet and MapPost directly on the application, and you can inject services as arguments of the endpoint expression.
What is the problem with putting all Minimal API endpoints in one file?
If you keep adding endpoints in the same file, the API becomes hard to maintain as it grows in complexity. Grouping endpoints by feature, with extension methods or a library like Carter, keeps related endpoints together and improves maintainability.
How do you organize Minimal API endpoints with extension methods?
Create a static class per feature with an extension method on IEndpointRouteBuilder that registers that feature's endpoints, then call it in Program. The cost is defining an extension method for each group of endpoints and remembering to call it during startup.
What is Carter and how does it help structure Minimal APIs?
Carter is an open source library with a module concept for grouping endpoints. You implement ICarterModule and define routes in AddRoutes, then call AddCarter and MapCarter in Program. Carter automatically registers the endpoints of every new module you add.
Should you use Minimal APIs in a real project?
I would be careful with Minimal APIs in very large applications, but they are worth exploring on smaller projects. A good use case is a microservice with a limited number of endpoints.



