LEARN BUILD GROW TOGETHER
Practical knowledge for developers
DEVELOPER LEARNING HUB
Learn. Build. Grow Together.

Practical tutorials, real-world examples and interview preparation.

.NETC#SQL SERVERANGULAR
Showing posts with label Web API Controllers. Show all posts

Minimal APIs vs Controllers in .NET Core Web API: Differences with Practical Examples

 

Introduction

When building a .NET Core Web API, one important architectural decision is how you want to define and organize your API endpoints.

.NET provides two commonly used approaches:

  1. Minimal APIs

  2. Controller-based APIs

Both approaches allow you to create HTTP endpoints, receive requests, execute application logic, and return responses to clients.

But their programming styles and organization are different.

Imagine you are developing an Employee Management application. You need endpoints to retrieve employees, create new employees, update employee details, and delete employee records.

Should you implement these endpoints directly in Program.cs using Minimal APIs, or should you create an EmployeesController?

Let's understand both approaches with practical C# examples.

1. What are Minimal APIs?

Minimal APIs were introduced in .NET 6 to provide a simplified way of building HTTP APIs with less boilerplate code.

Instead of creating separate controller classes with attributes, you can define API endpoints directly using methods such as:

  • MapGet()

  • MapPost()

  • MapPut()

  • MapPatch()

  • MapDelete()

These methods map incoming HTTP requests to the code responsible for processing them.

Minimal APIs are particularly useful for lightweight services, small applications, microservices, and APIs where a straightforward endpoint-based approach is sufficient.

Example: Creating an API using Minimal APIs

Create a new Web API project using the following command:

dotnet new web -n EmployeeMinimalApi

Open Program.cs.

Replace its contents with:

var builder = WebApplication.CreateBuilder(args);

var app = builder.Build();

var employees = new List<Employee>
{
    new Employee(101, "Rahul", "IT"),
    new Employee(102, "Priya", "HR")
};

// Get all employees
app.MapGet("/api/employees", () =>
{
    return Results.Ok(employees);
});

// Get employee by ID
app.MapGet("/api/employees/{id:int}", (int id) =>
{
    var employee = employees.FirstOrDefault(x => x.Id == id);

    return employee is not null
        ? Results.Ok(employee)
        : Results.NotFound();
});

// Create employee
app.MapPost("/api/employees", (Employee employee) =>
{
    var newId = employees.Count == 0
        ? 1
        : employees.Max(x => x.Id) + 1;

    var newEmployee = employee with { Id = newId };

    employees.Add(newEmployee);

    return Results.Created(
        $"/api/employees/{newEmployee.Id}",
        newEmployee);
});

// Update employee
app.MapPut("/api/employees/{id:int}",
    (int id, Employee updatedEmployee) =>
{
    var index = employees.FindIndex(x => x.Id == id);

    if (index == -1)
    {
        return Results.NotFound();
    }

    employees[index] = updatedEmployee with { Id = id };

    return Results.Ok(employees[index]);
});

// Delete employee
app.MapDelete("/api/employees/{id:int}", (int id) =>
{
    var employee = employees.FirstOrDefault(x => x.Id == id);

    if (employee is null)
    {
        return Results.NotFound();
    }

    employees.Remove(employee);

    return Results.NoContent();
});

app.Run();

public record Employee(int Id, string Name, string Department);

Let's understand what is happening here.

Step 1: Create the application

var builder = WebApplication.CreateBuilder(args);

var app = builder.Build();

This creates and builds the web application.

Step 2: Define the endpoints

app.MapGet("/api/employees", () =>
{
    return Results.Ok(employees);
});

This maps an HTTP GET request to /api/employees.

When a client calls this endpoint, the application returns the employee collection.

Step 3: Handle route parameters

app.MapGet("/api/employees/{id:int}", (int id) =>
{
    var employee = employees.FirstOrDefault(x => x.Id == id);

    return employee is not null
        ? Results.Ok(employee)
        : Results.NotFound();
});

Here, the employee ID comes from the URL and is provided to the endpoint handler.

Step 4: Return HTTP responses

Minimal APIs provide the Results class to create HTTP responses.

Examples:

Results.Ok(data);
Results.Created(location, data);
Results.NotFound();
Results.NoContent();

These results represent HTTP responses such as 200, 201, 404, and 204.

Testing Minimal API endpoints

Run your application and test the following requests using Postman.

HTTP methodEndpointPurpose
GET/api/employeesGet all employees
GET/api/employees/101Get employee by ID
POST/api/employeesCreate employee
PUT/api/employees/101Update employee
DELETE/api/employees/101Delete employee

For POST, use this JSON request body:

{
  "id": 0,
  "name": "Amit",
  "department": "Finance"
}

The API assigns the new ID.

The example uses an in-memory collection, so records are not permanently stored. Also, the PATCH operation is not implemented in this particular example; it can be added using MapPatch().

2. What are Controller-based APIs?

Controller-based APIs use controller classes to organize HTTP endpoints.

A controller is a class that typically inherits from ControllerBase and contains action methods that handle incoming requests.

Instead of defining all endpoints in Program.cs, you organize them into separate controller classes.

For example:

EmployeeManagement.Api
│
├── Controllers
│   └── EmployeesController.cs
│
├── Models
│   └── Employee.cs
│
└── Program.cs

This approach is commonly used in enterprise applications where APIs contain multiple endpoints and require clear separation of responsibilities.

Example: Creating an API using Controllers

Create a new Web API project:

dotnet new webapi --use-controllers -n EmployeeControllerApi

Open Program.cs:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers();

var app = builder.Build();

app.UseHttpsRedirection();

app.MapControllers();

app.Run();

Here:

  • AddControllers() registers the controller services.

  • MapControllers() maps controller endpoints into the application's routing system.

Now create a folder named Controllers and add EmployeesController.cs.

using Microsoft.AspNetCore.Mvc;

namespace EmployeeControllerApi.Controllers;

[ApiController]
[Route("api/[controller]")]
public class EmployeesController : ControllerBase
{
    private static readonly List<Employee> Employees =
    [
        new Employee
        {
            Id = 101,
            Name = "Rahul",
            Department = "IT"
        },
        new Employee
        {
            Id = 102,
            Name = "Priya",
            Department = "HR"
        }
    ];

    [HttpGet]
    public IActionResult GetEmployees()
    {
        return Ok(Employees);
    }

    [HttpGet("{id:int}")]
    public IActionResult GetEmployeeById(int id)
    {
        var employee = Employees.FirstOrDefault(x => x.Id == id);

        if (employee is null)
        {
            return NotFound();
        }

        return Ok(employee);
    }

    [HttpPost]
    public IActionResult CreateEmployee(Employee employee)
    {
        employee.Id = Employees.Count == 0
            ? 1
            : Employees.Max(x => x.Id) + 1;

        Employees.Add(employee);

        return CreatedAtAction(
            nameof(GetEmployeeById),
            new { id = employee.Id },
            employee);
    }

    [HttpPut("{id:int}")]
    public IActionResult UpdateEmployee(
        int id,
        Employee updatedEmployee)
    {
        var employee = Employees.FirstOrDefault(x => x.Id == id);

        if (employee is null)
        {
            return NotFound();
        }

        employee.Name = updatedEmployee.Name;
        employee.Department = updatedEmployee.Department;

        return Ok(employee);
    }

    [HttpDelete("{id:int}")]
    public IActionResult DeleteEmployee(int id)
    {
        var employee = Employees.FirstOrDefault(x => x.Id == id);

        if (employee is null)
        {
            return NotFound();
        }

        Employees.Remove(employee);

        return NoContent();
    }
}

Create the Employee.cs model:

namespace EmployeeControllerApi;

public class Employee
{
    public int Id { get; set; }

    public string Name { get; set; } = string.Empty;

    public string Department { get; set; } = string.Empty;
}

Understanding the controller code

Let's understand the important parts.

1. Controller declaration

[ApiController]
[Route("api/[controller]")]
public class EmployeesController : ControllerBase
  • [ApiController] enables API-specific conventions and behaviors, including automatic model validation responses in supported scenarios.

  • [Route] defines the route pattern.

  • [controller] is replaced by the controller name without the Controller suffix.

  • ControllerBase provides functionality commonly needed when building APIs.

Therefore, the base route becomes:

/api/employees

2. HTTP GET action

[HttpGet]
public IActionResult GetEmployees()
{
    return Ok(Employees);
}

The [HttpGet] attribute maps GET requests to this action.

3. HTTP POST action

[HttpPost]
public IActionResult CreateEmployee(Employee employee)
{
    // Create employee
}

The incoming request body is bound to the Employee parameter.

4. HTTP PUT action

[HttpPut("{id:int}")]
public IActionResult UpdateEmployee(int id, Employee updatedEmployee)

This action accepts the employee ID from the URL and employee information from the request body.

5. HTTP DELETE action

[HttpDelete("{id:int}")]
public IActionResult DeleteEmployee(int id)

This action identifies the employee using the route ID and removes the corresponding record.

The controller approach organizes multiple endpoints within a dedicated class rather than placing all endpoint definitions inside Program.cs.

3. Minimal APIs vs Controllers: Key differences

Now that we have implemented both approaches, let's compare them.

FeatureMinimal APIsControllers
Programming styleEndpoint-basedClass and action-based
Code structureCan be compactUses controller classes
RoutingMapGet(), MapPost() etc.Routing attributes
Request handlingEndpoint handlersController action methods
HTTP responsesResults and typed resultsIActionResult, ActionResult<T>
OrganizationFlexible; can use route groups and extension methodsNaturally organized by controllers
FiltersEndpoint filtersMVC filters and other MVC extensibility
Model validationCan be implemented using endpoint logic, validation libraries, and framework features[ApiController] provides automatic model validation responses for invalid models
Dependency injectionSupportedSupported
Suitable forLightweight endpoints and focused servicesStructured APIs and controller-oriented applications

Important: Both approaches support routing, dependency injection, authentication, authorization, and other application capabilities. The choice is mainly about programming style, organization, and the features your application needs.

4. Advantages of Minimal APIs

Minimal APIs provide several benefits.

Less boilerplate code

You can create endpoints without defining a separate controller class for every API group.

Simple endpoint definitions

HTTP methods and their handlers can be defined directly using methods such as MapGet() and MapPost().

Useful for small services

When an application has a limited number of endpoints, Minimal APIs can provide a straightforward implementation.

Support for route groups

You can group related endpoints together and apply shared route prefixes or endpoint configuration.

For example:

var employeeGroup = app.MapGroup("/api/employees");

employeeGroup.MapGet("/", () => "Get employees");

employeeGroup.MapGet("/{id:int}", (int id) =>
    $"Get employee {id}");

Both endpoints share the /api/employees route prefix.

This helps organize Minimal APIs as the number of endpoints grows.

5. Advantages of Controller-based APIs

Controllers are useful when you want an organized structure for larger applications.

Clear separation of endpoints

Related API operations can be grouped inside a controller class.

For example:

Controllers
│
├── EmployeesController.cs
├── DepartmentsController.cs
├── ProductsController.cs
└── OrdersController.cs

Attribute-based routing

You can define routes using attributes such as:

[HttpGet]
[HttpPost]
[HttpPut("{id}")]
[HttpDelete("{id}")]

MVC features

Controller-based APIs support MVC features such as:

  • Action filters

  • Model binding

  • Model validation

  • Controller conventions

  • MVC-specific extensibility

Familiar structure for enterprise applications

Teams already working with MVC and controller-based architectures may find this approach easier to maintain and extend.

6. Can Minimal APIs support Dependency Injection?

Yes. Both Minimal APIs and Controllers support Dependency Injection.

Dependency Injection allows you to provide required services to application components rather than creating those dependencies manually everywhere.

For example, imagine you have an IEmployeeService.

First, register the service:

builder.Services.AddScoped<IEmployeeService, EmployeeService>();

In a Minimal API, you can inject it into an endpoint handler:

app.MapGet("/api/employees",
    (IEmployeeService employeeService) =>
{
    return employeeService.GetAllEmployees();
});

In a controller, you can inject it through the constructor:

[ApiController]
[Route("api/[controller]")]
public class EmployeesController : ControllerBase
{
    private readonly IEmployeeService _employeeService;

    public EmployeesController(
        IEmployeeService employeeService)
    {
        _employeeService = employeeService;
    }

    [HttpGet]
    public IActionResult GetEmployees()
    {
        return Ok(_employeeService.GetAllEmployees());
    }
}

The important point is that Dependency Injection is not exclusive to either approach.

We'll explore Dependency Injection in detail in Day 8 of this series.

7. When should you use Minimal APIs?

Minimal APIs are a good choice when:

  • You are building a small API service.

  • You need a limited number of HTTP endpoints.

  • You are developing lightweight microservices.

  • You want a straightforward endpoint-based programming model.

  • Your team prefers explicit endpoint registration and organization.

For example, imagine an internal service that exposes only a few endpoints to retrieve application configuration or check service health.

Minimal APIs may be a suitable choice.

However, Minimal APIs can also be used in larger applications. You can organize them with route groups, extension methods, and separate endpoint-registration files.

8. When should you use Controllers?

Controllers are a good choice when:

  • Your application has many related API endpoints.

  • You prefer organizing endpoints into controller classes.

  • You need MVC-specific features such as action filters.

  • Your team already follows a controller-based architecture.

  • You want a familiar structure for a larger API codebase.

For example, an Employee Management application may eventually contain:

  • Employee management

  • Department management

  • Attendance management

  • Payroll management

  • Reporting

  • Role-based access control

A controller-based structure can make it easier to organize these related areas.

Again, this does not mean Minimal APIs cannot handle such an application. Both approaches can be used with a well-designed architecture.

9. Which approach is better: Minimal APIs or Controllers?

There is no single answer that applies to every application.

The decision depends on the application requirements, team experience, architecture, and the framework features you need.

Consider this practical comparison:

Scenario 1: Small internal service

You need five endpoints to expose application health and configuration information.

Minimal APIs may be a convenient choice because they allow you to define the endpoints with relatively little setup.

Scenario 2: Large Employee Management system

You have multiple business modules, several developers, complex request handling, and MVC-specific requirements.

Controllers may provide a more familiar organizational structure.

Scenario 3: Existing controller-based application

Your organization already has a controller-based Web API with established conventions and reusable components.

Continuing with controllers may be the most practical decision. You do not need to rewrite the application simply because Minimal APIs are available.

My recommendation: Choose the approach based on actual requirements, not just the amount of code. A maintainable API with clear responsibilities is more important than choosing the approach with the fewest lines.

10. Important points developers should remember

Before moving forward, keep these points in mind:

  1. Minimal APIs were introduced in .NET 6.

  2. Both Minimal APIs and Controllers can build HTTP APIs.

  3. Both approaches support Dependency Injection.

  4. Minimal APIs use endpoint mapping methods such as MapGet() and MapPost().

  5. Controllers use controller classes, action methods, and routing attributes.

  6. Minimal APIs support route groups and endpoint filters.

  7. Controllers support MVC features such as action filters and controller conventions.

  8. Neither approach automatically makes your API secure or production-ready. You must configure the appropriate security, validation, error handling, and monitoring features.

11. Interview questions

Q1. What are Minimal APIs in .NET?

Minimal APIs provide a simplified way to build HTTP APIs by defining endpoints directly using methods such as MapGet(), MapPost(), MapPut(), and MapDelete().

Q2. In which .NET version were Minimal APIs introduced?

Minimal APIs were introduced in .NET 6.

Q3. What is the difference between Minimal APIs and Controllers?

Minimal APIs define endpoints using endpoint mapping methods, while Controllers organize API endpoints inside classes containing action methods.

Q4. Can we use Dependency Injection in Minimal APIs?

Yes. Minimal APIs support Dependency Injection. Registered services can be injected directly into endpoint handlers.

Q5. Do Minimal APIs support authentication and authorization?

Yes. Minimal APIs support authentication and authorization through the ASP.NET Core authentication and authorization infrastructure.

Q6. What is MapGet()?

MapGet() maps an HTTP GET request to an endpoint handler.

Q7. What is MapControllers()?

MapControllers() maps controller action endpoints into the application's routing system.

Q8. Can Minimal APIs be used for large applications?

Yes. Minimal APIs can be organized using route groups, extension methods, and separate endpoint-registration files. They are not limited to small applications.

Q9. Can we use Minimal APIs and Controllers in the same project?

Yes. A .NET application can contain both Minimal API endpoints and controller-based endpoints.

Q10. Which approach is faster?

Performance depends on the actual application, workload, configuration, and implementation. You should benchmark your application's real requirements rather than assuming one approach is always faster.

Conclusion

Minimal APIs and Controller-based APIs are two supported approaches for building modern .NET Core Web APIs.

Minimal APIs provide a straightforward endpoint-based programming model, while Controllers provide a class-based structure with MVC-specific features.

In this article, we implemented the same Employee Management functionality using both approaches and compared their structure, advantages, and use cases.

The most important takeaway is that both approaches can build maintainable and production-ready APIs when designed properly.

What's next?

In Day 5, we'll explore Routing and Route Parameters in .NET Core Web API.

We'll understand how API URLs are mapped to endpoints, how route parameters work, how to use optional parameters and constraints, and how to design clean and predictable API routes with practical C# examples.


Skills Tutorials – Learn • Build • Grow Together

Continue learning with our 30-day .NET Core Web API series.