This document outlines common REST API design mistakes in ASP.NET Core (.NET 9.0) and provides practical tips to avoid them, ensuring scalable, maintainable, and secure APIs. It leverages features introduced or enhanced in .NET 9.0, such as improved minimal APIs, better OpenAPI support, enhanced performance, and native AOT compatibility.
1. Using Incorrect HTTP Methods
Mistake : Treating all operations as POST requests, ignoring semantic HTTP methods.
Why it's bad : Violates REST principles, confuses clients, and hinders caching/interoperability.
Tip : Use appropriate HTTP verbs: GET (read), POST (create), PUT (update), PATCH (partial update), DELETE (remove).
Bad Example
[HttpPost("getUser")]
public IActionResult GetUser(int id)
{
var user = _userService.GetById(id);
return Ok(user);
}
Good Example
[HttpGet("{id}")]
public async Task<IActionResult> GetUser(int id)
{
var user = await _userService.GetByIdAsync(id);
return user != null ? Ok(user) : NotFound();
}
2. Poor Resource Naming Conventions
Mistake : Using verbs in URIs like /getUsers or /createUser.
Why it's bad : URIs should represent resources, not actions. Leads to inconsistent and hard-to-maintain APIs.
Tip : Use nouns for resources, keep URIs simple and hierarchical. Use query parameters for filtering.
Bad Example
GET /getAllUsers
POST /createNewUser
Good Example
GET /api/users
GET /api/users?status=active
POST /api/users
3. Ignoring HTTP Status Codes
Mistake : Always returning 200 OK, even for errors or not found resources.
Why it's bad : Clients can't distinguish success from failure, leading to poor error handling.
Tip : Return appropriate status codes (200, 201, 400, 401, 404, 500, etc.) and use ProblemDetails for errors.
Bad Example
[HttpGet("{id}")]
public IActionResult GetUser(int id)
{
var user = _userService.GetById(id);
return Ok(new { success = user != null, data = user });
}
Good Example
[HttpGet("{id}")]
public async Task<IActionResult> GetUser(int id)
{
var user = await _userService.GetByIdAsync(id);
if (user == null)
return NotFound(new ProblemDetails { Title = "User not found" });
return Ok(user);
}
4. Not Versioning Your API
Mistake : No versioning, breaking changes affect all clients immediately.
Why it's bad : Backward compatibility issues, forces all clients to update simultaneously.
Tip : Use URL versioning (e.g., /api/v1/users) or header-based versioning. Plan for deprecation.
Example
[ApiController]
[Route("api/v{version:apiVersion}/users")]
[ApiVersion("1.0")]
public class UsersController : ControllerBase
{
// Controller methods
}
5. Over-fetching or Under-fetching Data
Mistake : Returning all fields always or too few, forcing clients to make multiple requests.
Why it's bad : Inefficient network usage, poor performance.
Tip : Use DTOs (Data Transfer Objects) and allow field selection via query parameters.
Example
[HttpGet]
public async Task<IActionResult> GetUsers([FromQuery] string fields = null)
{
var users = await _userService.GetAllAsync();
if (!string.IsNullOrEmpty(fields))
{
var selectedFields = fields.Split(',');
// Apply field selection logic
}
return Ok(users);
}
6. Synchronous Operations in Controllers
Mistake : Using synchronous methods in ASP.NET Core controllers.
Why it's bad : Blocks threads, reduces scalability under load.
Tip : Always use async/await for I/O operations.
Bad Example
[HttpGet]
public IActionResult GetUsers()
{
var users = _userService.GetAll(); // Synchronous
return Ok(users);
}
Good Example
[HttpGet]
public async Task<IActionResult> GetUsers()
{
var users = await _userService.GetAllAsync();
return Ok(users);
}
7. Exposing Internal Exceptions
Mistake : Letting framework exceptions bubble up to clients.
Why it's bad : Security risk (information disclosure), poor user experience.
Tip : Use global exception handling with custom error responses.
Example (in Startup.cs or Program.cs)

Join the conversation! Your thoughts help the community grow.