Sitemap
Press enter or click to view image in full size

ASP.NET Core MVC Controller vs. minimal API vs. FastEndpoints — What’s the Best for Performance ?

5 min readApr 21, 2025

At least, there are three different ways to build APIs — but which one is the best of the best?

Could it be the legacy MVC Controller, the modern Minimal API, or maybe the external FastEndpoints library?

Let’s figure it out.

To do that, I’m going to create a simple Web API using each approach and measure performance using k6.

First of all, I’ll create an entity that will be used in the DbContext.

It’s a very simple class with just two properties:

public class User
{
[Key]
public Guid Id { get; set; }

[Required]
[MaxLength(100)]
public string Email { get; set; } = null!;
}

The entity will be added to my DbContext :

public sealed class UsersDbContext (DbContextOptions options)
: DbContext(options)
{
public DbSet<User> Users { get; set; } = null!;
}

On top of the DbContext, I’ll create a common service that will be used in all Web APIs.

The service will have methods to get, create, and delete users:

public sealed class UsersService(UsersDbContext dbContext)
: IUsersService
{
public async Task<IReadOnlyCollection<User>> GetAsync()
{
var users = await dbContext.Users.ToListAsync();

return users;
}

public async Task CreateAsync(string email)
{
var user = new User
{
Id = Guid.CreateVersion7(),
Email = email
};

await dbContext.Users.AddAsync(user);

await dbContext.SaveChangesAsync();
}

public async Task DeleteFirstAsync()
{
var firstUser = await dbContext.Users.FirstAsync();

dbContext.Users.Remove(firstUser);

await dbContext.SaveChangesAsync();
}
}

Next, let’s create a controller and inject the service we just created into it:

[Route("api/v1/[controller]")]
[ApiController]
public sealed class HomeController(IUsersService service) : ControllerBase
{
[HttpGet]
public async Task<IActionResult> GetUsers()
{
var users = await service.GetAsync();

return Ok(users);
}

[HttpPost]
public async Task<IActionResult> CreateUser(CreateUserRequest request)
{
await service.CreateAsync(request.Email);

return NoContent();
}

[HttpDelete]
public async Task<IActionResult> DeleteUser()
{
await service.DeleteFirstAsync();

return NoContent();
}
}

CreateUserRequest is a record with the following source:

public sealed record CreateUserRequest(string Email);

Next up, I’m going to create endpoints for the Minimal API:

app.MapGet("api/v1/home", async ([FromServices] IUsersService service) =>
{
var users = await service.GetAsync();

return TypedResults.Ok(users);
});

app.MapPost("api/v1/home", async (
[FromServices] IUsersService service,
[FromBody] CreateUserRequest request) =>
{
await service.CreateAsync(request.Email);

return TypedResults.NoContent();
});

app.MapDelete("api/v1/home", async ([FromServices] IUsersService service) =>
{
await service.DeleteFirstAsync();

return TypedResults.NoContent();
});

The last one is going to be FastEndpoints.

Here you can find the GetUserEndpoint:

public sealed class GetUsersEndpoint(IUsersService service)
: EndpointWithoutRequest<Ok<IReadOnlyCollection<User>>>
{
public override void Configure()
{
Get("api/v1/home");
AllowAnonymous();
}

public override async Task<Ok<IReadOnlyCollection<User>>> ExecuteAsync(
CancellationToken ct)
{
var users = await service.GetAsync();

return TypedResults.Ok(users);
}
}

Next is the CreateUserEndpoint:

public sealed class CreateUserEndpoint(IUsersService service)
: Endpoint<CreateUserRequest, NoContent>
{
public override void Configure()
{
Post("api/v1/home");
AllowAnonymous();
}

public override async Task HandleAsync(
CreateUserRequest req,
CancellationToken ct)
{
await service.CreateAsync(req.Email);

await SendAsync(TypedResults.NoContent(), cancellation: ct);
}
}

And finally, the DeleteUserEndpoint:

public sealed class DeleteUserEndpoint(IUsersService service)
: EndpointWithoutRequest<NoContent>
{
public override void Configure()
{
Delete("api/v1/home");
AllowAnonymous();
}

public override async Task HandleAsync(CancellationToken ct)
{
await service.DeleteFirstAsync();

await SendAsync(TypedResults.NoContent(), cancellation: ct);
}
}

If you’re interested in learning more about FastEndpoints, feel free to check out my video: https://youtu.be/clLtFxomzv8

At the beginning of the test, I reset the database to its initial state — with 10k entities in it.
Each test was run one by one to ensure consistent and isolated results.

Get Users Case:

  • MVC Controller
    request duration: avg=445.52ms
    memory usage: avg = ~194–230 MB
Press enter or click to view image in full size
Get Users Case: MVC Controller consuming memory
  • Minimal API
    request duration: avg=430.31ms
    memory usage: avg-~184–220 MB
Press enter or click to view image in full size
Get Users Case: Minimal API consuming memory
  • FastEndpoints
    request duration: avg=436.17ms
    memory usage: avg: ~190–225 MB
Press enter or click to view image in full size
Get Users Case: FastEndpoints consuming memory

As we can see, Minimal API has the lowest average request duration and memory usage.

FastEndpoints comes in second, followed by the MVC Controller as the slowest.

Create User Case:

  • MVC Controller
    request duration: avg=75.75ms
    memory usage: avg =~70–75 MB
Press enter or click to view image in full size
Create User Case: MVC Controller consuming memory
  • Minimal API
    request duration: avg=72.98ms
    memory usage: avg=~60–70MB
Press enter or click to view image in full size
Create User Case: Minimal API consuming memory
  • FastEndpoints
    request duration: avg=91.09ms
    memory usage: avg= ~62–72 MB
Press enter or click to view image in full size
Create User Case: FastEndpoints consuming memory

The results show that Minimal API once again delivered the best performance.
Notably, FastEndpoints had the lowest performance in this test, however, memory usage is better that for MVC Controller.

Delete User Case:

  • MVC Controller
    request duration: avg=1.53s
    memory usage: avg = ~86–90 MB
Press enter or click to view image in full size
Delete User Case: MVC Controller consuming memory
  • Minimal API
    request duration: avg=1.38s
    memory usage: avg= ~80–85 MB
Press enter or click to view image in full size
Delete User Case: Minimal API consuming memory
  • FastEndpoints
    request duration: avg=1.51s
    memory usage: avg= ~80-84 MB
Press enter or click to view image in full size

Overall, Minimal API demonstrated the best performance.

FastEndpoints came in second, while the MVC Controller showed the weakest results.

Across all test cases, the Minimal API consistently delivered the best performance, making it the most efficient option overall.

FastEndpoints showed solid results in most scenarios.

The familiar MVC Controller had the lowest performance.

As alway you can find the code in the following link.

--

--

Denis Makarenko
Denis Makarenko

Written by Denis Makarenko

I'm .NET Engineer with years of experience in the field

Responses (4)