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:
Minimal APIs
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 EmployeeMinimalApiOpen 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 method | Endpoint | Purpose |
|---|---|---|
| GET | /api/employees | Get all employees |
| GET | /api/employees/101 | Get employee by ID |
| POST | /api/employees | Create employee |
| PUT | /api/employees/101 | Update employee |
| DELETE | /api/employees/101 | Delete 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.csThis 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 EmployeeControllerApiOpen 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 theControllersuffix.ControllerBaseprovides functionality commonly needed when building APIs.
Therefore, the base route becomes:
/api/employees2. 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.
| Feature | Minimal APIs | Controllers |
|---|---|---|
| Programming style | Endpoint-based | Class and action-based |
| Code structure | Can be compact | Uses controller classes |
| Routing | MapGet(), MapPost() etc. | Routing attributes |
| Request handling | Endpoint handlers | Controller action methods |
| HTTP responses | Results and typed results | IActionResult, ActionResult<T> |
| Organization | Flexible; can use route groups and extension methods | Naturally organized by controllers |
| Filters | Endpoint filters | MVC filters and other MVC extensibility |
| Model validation | Can be implemented using endpoint logic, validation libraries, and framework features | [ApiController] provides automatic model validation responses for invalid models |
| Dependency injection | Supported | Supported |
| Suitable for | Lightweight endpoints and focused services | Structured 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.csAttribute-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:
Minimal APIs were introduced in .NET 6.
Both Minimal APIs and Controllers can build HTTP APIs.
Both approaches support Dependency Injection.
Minimal APIs use endpoint mapping methods such as
MapGet()andMapPost().Controllers use controller classes, action methods, and routing attributes.
Minimal APIs support route groups and endpoint filters.
Controllers support MVC features such as action filters and controller conventions.
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.