.NET Minimal API

light suspended in front of teal wall

Implementation speed is critical in our domain. Beating the competition to market is what will bring you and your organization to the top of the pile. If you have ever written an API using ExpressJS you will understand why the framework is so popular. In just a handful of lines, you can have an API up and ready to receive calls.

Coming from a .NET shop, dropping everything to build APIs in NodeJS is not the easiest task. The tooling is different, it is not likely that the current team knows the intricacies of NodeJS and ExpressJS, and the team may not be interested in being NodeJS developers. If only the .NET team implemented an API using minimal code...

Upgrade to .NET 6 and you are in luck! Building off the backbone established in ASP.NET Core with the application pipeline middleware. This is just how simple it can be to create an API in .NET 6:

var builder = WebApplication.CreateBuilder(args);

var app = builder.Build();

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

app.Run();

4 lines of code are all it takes to implement a simple API now. Not only is this fast, but it's easy to understand for new developers. Looking for another positive? The toolchain that your development teams already know and love are the ones that are used. Here is a breakdown of the minimal API sample. First, we need an instance of a WebApplicationBuilder. But, what are these args? Another upgrade in .NET 6 allows developers to create command-line applications with less boilerplate code. The above is an example of that as well. The args being passed into CreateBuilder are the command-line arguments set when the application is started. Next, we need the instance of the built application. With the build application in hand, we can then add all of our routes to the application directly. In the above sample, we have added a GET request at the root of the application. Finally, we need to start the application execution by calling Run.

Now, not all API implementations are this short. So what does a more complete example look like?

using Microsoft.EntityFrameworkCore;

var builder = WebApplication.CreateBuilder(args);

// Register the Person repository
builder.Services.AddDbContext<PersonRepository>(options => options.UseInMemoryDatabase("PersonRepository"));

var app = builder.Build();

// Adding a default route greet the user
app.MapGet("/", () => Results.Ok(new { Message = "Welcome to the person API!"}));

// Route to fetch a single person by id
app.MapGet("/person/{id}", async (Guid id, PersonRepository repo) => Results.Ok(await repo.Persons.FirstOrDefaultAsync(p => p.Id == id)));

// Route to fetch all people
app.MapGet("/person", async (PersonRepository repo) => Results.Ok(await repo.Persons.ToListAsync()));

// Route to create new person
app.MapPost("/person", async (Person person, PersonRepository repo) =>
{
    if (person == null)
        Results.BadRequest();
    await repo.Persons.AddAsync(person);
    await repo.SaveChangesAsync();
    Results.Ok();
});

app.Run();

Notice there are a couple of HTTP methods used MapGet and MapPost. There is an in-memory data store that used PersonRepository to persist the data while executing. To me, the 2 endpoints of interest are the GET person by id and the POST of a new person. Similar to the Web API routing, in this implementation, we can also define tokens in the route. Note the {id} token in the path. That token then becomes a typed argument on our anonymous function. Given that there is also a dependency on the PersonRepository, we also inject that right into the anonymous function. Since the function executes asynchronously the anonymous function is prefixed with async. Similar to the POST endpoint, the only difference is the first argument passed into the anonymous function is the body of the request.

The last thing to touch on here is testing and discovering this API. Swagger is one tool that comes out of the box in .NET 6. Before, calling builder.Build() one has to add:

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

Then, similar to adding a new route to the app, we have to add:

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

Starting the application using dotnet run then navigating to /swagger adn viola! A quick and easy minimal API implementation is up and running ~40 lines of code.

In summary, this implementation from the .NET team has been great. The platform continues to grow in the areas that the consumers need. Bringing quick and easy to consume solutions to everyday problems.