How To Structure Minimal APIs

Milan Jovanović - Sep 16 '23 - - Dev Community

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();
Enter fullscreen mode Exit fullscreen mode

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);
        });
    }
}
Enter fullscreen mode Exit fullscreen mode

And then inside of Program we need to register the endpoints:

app.RegisterProductsEndpoints();
Enter fullscreen mode Exit fullscreen mode

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);
        });
    }
}
Enter fullscreen mode Exit fullscreen mode

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();
Enter fullscreen mode Exit fullscreen mode

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.


P.S. Whenever you're ready, there are 2 ways I can help you:

  1. Pragmatic Clean Architecture: This comprehensive course will teach you the system I use to ship production-ready applications using Clean Architecture. Learn how to apply the best practices of modern software architecture. Join 950+ students here.

  2. Patreon Community: Think like a senior software engineer with access to the source code I use in my YouTube videos and exclusive discounts for my courses. Join 820+ engineers here.

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .