- Home
- .NET tutorials
- How to use routing in Minimal APIs with one line of code
How to use routing in Minimal APIs with one line of code
Published: Monday 11 August 2025
I was skeptical when I first came across Minimal APIs in ASP.NET Core. It looked too simple! How can I write a "Hello world" response with just one line of code?
I'm used to Web API controllers which involves creating a class, which needs a namespace and possibly a constructor. Then I need a method which needs a Route
attribute assigned to it before I can finally return my "Hello world" response.
// HelloWorldController.cs
using Microsoft.AspNetCore.Mvc;
namespace RoundTheCode.MinimalApi.Controllers;
[Route("api/[controller]")]
[ApiController]
public class HelloWorldController : ControllerBase
{
public HelloWorldController()
{
}
[HttpGet("/hello-world")]
public string HelloWorld()
{
return "Hello world";
}
}
But that's what I'm used to. However, I could no longer ignore the hype of Minimal APIs and that has forced me to dive and see what it was all about.
And here we will cover what I learnt about routing.
Get started with one line of code
With Minimal APIs, I wrote this line in Program.cs
:
app.MapGet("/hello-world", () => "Hello, world!");
It felt wrong. No controller. No unnecessary boiler plate code. But I ran it, and it worked.
As there is a MapGet
method, I assumed there was also a MapPost
, MapPut
and MapDelete
. And I was right!
Like with controllers, you can bind the request to a model and then use it within the endpoint. You can also add a routing parameter.
// Program.cs
app.MapGet("/todo/{id}", (int id, IToDoService toDoService) =>
{
var item = toDoService.Get(id);
if (item == null)
{
return Results.NotFound();
}
return Results.Ok(item);
});
app.MapPost("/todo", (CreateToDoItemDto createItem, IToDoService toDoService) =>
{
toDoService.Create(createItem);
return Results.Ok();
});
app.MapPut("/todo/{id}", (int id, UpdateToDoItemDto updateItem, IToDoService toDoService) =>
{
toDoService.Update(id, updateItem);
return Results.Ok();
});
app.MapDelete("/todo/{id}", (int id, IToDoService toDoService) =>
{
toDoService.Delete(id);
return Results.Ok();
});
This is great I thought to myself. A much more simplier way of writing API endpoints. But what if I wanted to combine more than one HTTP method to the same endpoint? Well that's where MapMethods
comes in. This allows you to specify which HTTP methods you wish to use with your endpoint.
// Program.cs
app.MapMethods("/with-get-head", new[] { "GET", "HEAD"}, () => "Hello world");
Different ways to return a response
Minimal APIs allows you to return a response in multiple ways depending on the complexity of your logic. I'm used to using lambda expressions which are the examples that I've shown so far:
// Program.cs
app.MapGet("/hello-world", () => "Hello, world!");
You can also create a local function:
// Program.cs
string HelloWorld() => "Hello world";
app.MapGet("/hello-world/local", HelloWorld);
You can also call a method from a class instance:
// Program.cs
var helloWorldHandler = new HelloWorldHandler();
app.MapGet("/hello-world/class-instance", helloWorldHandler.Print);
app.Run();
public class HelloWorldHandler
{
public string Print()
{
return "Hello world";
}
}
There is also a way to call a static method:
// Program.cs
app.MapGet("/hello-world/static", HelloWorldStaticHandler.Print);
app.Run();
public class HelloWorldStaticHandler
{
public static string Print()
{
return "Hello world";
}
}
Getting out of Program.cs
Things got crowded fast! At this point, my Program.cs
file was looking quite messy:
// Program.cs
using RoundTheCode.MinimalApi.Dto;
using RoundTheCode.MinimalApi.Services;
using Scalar.AspNetCore;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddControllers();
// Learn more about configuring OpenAPI at https://aka.ms/aspnet/openapi
builder.Services.AddOpenApi();
builder.Services.AddSingleton<IToDoService, ToDoService>();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.MapOpenApi();
app.MapScalarApiReference(options =>
{
options.WithTitle("RoundTheCode.DI");
options.WithTheme(ScalarTheme.Alternate);
options.WithSidebar(true);
});
}
// Lamba expression
app.MapGet("/", () => "Hello world");
app.MapMethods("/with-get-head", new[] { "GET", "HEAD"}, () => "Hello world");
// Local function
string HelloWorld() => "Hello world";
app.MapGet("/local", HelloWorld);
// Instance method
var helloWorldHandler = new HelloWorldHandler();
app.MapGet("/class-instance", helloWorldHandler.Print);
// Static method
app.MapGet("/static", HelloWorldStaticHandler.Print);
// Todo endpoints
app.MapGet("/todo/{id}", (int id, IToDoService toDoService) =>
{
var item = toDoService.Get(id);
if (item == null)
{
return Results.NotFound();
}
return Results.Ok(item);
});
app.MapPost("/todo", (CreateToDoItemDto createItem, IToDoService toDoService) =>
{
toDoService.Create(createItem);
return Results.Ok();
});
app.MapPut("/todo/{id}", (int id, UpdateToDoItemDto updateItem, IToDoService toDoService) =>
{
toDoService.Update(id, updateItem);
return Results.Ok();
});
app.MapDelete("/todo/{id}", (int id, IToDoService toDoService) =>
{
toDoService.Delete(id);
return Results.Ok();
});
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
app.Run();
public class HelloWorldHandler
{
public string Print()
{
return "Hello world";
}
}
public class HelloWorldStaticHandler
{
public static string Print()
{
return "Hello world";
}
}
But then I discovered you could move the endpoints into a separate static class:
// ToDoEndpoints.cs
public static class ToDoEndpoints
{
public static void MapToDoEndpoints(this WebApplication app)
{
app.MapGet("/todo/{id}", (int id, IToDoService toDoService) =>
{
var item = toDoService.Get(id);
if (item == null)
{
return Results.NotFound();
}
return Results.Ok(item);
});
app.MapPost("/todo", (CreateToDoItemDto createItem, IToDoService toDoService) =>
{
toDoService.Create(createItem);
return Results.Ok();
});
app.MapPut("/todo/{id}", (int id, UpdateToDoItemDto updateItem, IToDoService toDoService) =>
{
toDoService.Update(id, updateItem);
return Results.Ok();
});
app.MapDelete("/todo/{id}", (int id, IToDoService toDoService) =>
{
toDoService.Delete(id);
return Results.Ok();
});
}
}
Then you can call the static method in Program.cs
to register them:
// Program.cs
app.MapToDoEndpoints();
/*
app.MapGet("/todo/{id}", (int id, IToDoService toDoService) =>
{
var item = toDoService.Get(id);
if (item == null)
{
return Results.NotFound();
}
return Results.Ok(item);
});
app.MapPost("/todo", (CreateToDoItemDto createItem, IToDoService toDoService) =>
{
toDoService.Create(createItem);
return Results.Ok();
});
app.MapPut("/todo/{id}", (int id, UpdateToDoItemDto updateItem, IToDoService toDoService) =>
{
toDoService.Update(id, updateItem);
return Results.Ok();
});
app.MapDelete("/todo/{id}", (int id, IToDoService toDoService) =>
{
toDoService.Delete(id);
return Results.Ok();
});
*/
Groups: A way to resolve endpoints with common paths
I also found a way of how you can organise your endpoints further. Using groups, you can bundle routes that share a common path.
In this example, we've created a group with a route of /todo-group
. When we call the Map
extension methods in the group, it will prepend the /todo-group
route to each of the endpoints.
So when calling the toDoGroup.MapGet
example below, the route will be /todo-group/{id}
:
// ToDoEndpoints.cs
public static class ToDoEndpoints
{
public static void MapToDoGroup(this WebApplication app)
{
var toDoGroup = app.MapGroup("/todo-group");
toDoGroup.MapGet("/{id}", (int id, IToDoService toDoService) =>
{
var item = toDoService.Get(id);
if (item == null)
{
return Results.NotFound();
}
return Results.Ok(item);
});
toDoGroup.MapPost("/", (CreateToDoItemDto createItem, IToDoService toDoService) =>
{
toDoService.Create(createItem);
return Results.Ok();
});
toDoGroup.MapPut("/{id}", (int id, UpdateToDoItemDto updateItem, IToDoService toDoService) =>
{
toDoService.Update(id, updateItem);
return Results.Ok();
});
toDoGroup.MapDelete("/{id}", (int id, IToDoService toDoService) =>
{
toDoService.Delete(id);
return Results.Ok();
});
}
}
// Program.cs
app.MapToDoGroup();
Watch the video
To really show you how impressive Minimal APIs are, I recorded this video so you can see how you can set up the different ways of routing:
And when you watch the video, you can download the code example to follow along and try it out for yourself. This will enable you to experience the benefits of Minimal APIs.
Final thoughts
I started out doubtful. Minimal APIs looked too simple. But the more I got into it, the more I saw the benefits to it.
It doesn't replace controllers for larger apps. Not yet anyways. But it's fast, readable and has progressed a lot since its launch in .NET 6.
I feel there is much more to discover!
Latest tutorials

