Blog Details

story

Quality Practices for REST API Development in .NET / Others

Developing a REST API might seem straightforward until quality becomes a critical metric for evaluation. Ensuring high quality and performance requires adhering to specific practices and actions. Here are some key practices to consider, specifically within the context of .NET, but also applicable to other technologies

namespace MyApi.Controllers
{
public class User
{
public string Id { get; set; }
public string Name { get; set; }
public string Email { get; set; }
}

// This attribute sets the route for the controller, making it accessible at /api/users
[Route("api/[controller]")]
[ApiController]
public class UsersController : ControllerBase
{
// This is a simple in-memory data store for demonstration purposes
private static readonly Dictionary<string, User> Users = new Dictionary<string, User>
{
{ "1", new User { Id = "1", Name = "User One", Email = "user1@example.com" } },
{ "2", new User { Id = "2", Name = "User Two", Email = "user2@example.com" } }
};

// GET /api/users/{id} - Retrieves a user by their ID
[HttpGet("{id}")]
public ActionResult GetUser(string id)
{
if (Users.TryGetValue(id, out var user))
{
return Ok(user);
}
return NotFound();
}
}
}

2. Avoid Modeling URLs After Database Structures
It’s a common mistake to reflect the entire relational model in the URL structure. For instance, GET /users/{user_id} is preferable to GET /departments/{department_id}/users/{user_id}. Since user IDs are globally unique, there is no need to include the department ID in the URL.

3. Do Not Return Arrays as Top-Level Responses
Top-level responses from endpoints should be objects, not arrays. For example, instead of having GET /users return [{...user1...}, {...user2...}], it should return { "data": [{...user1...}, {...user2...}] }. Arrays at the top level can make it difficult to implement changes that are backward compatible, such as adding a pagination field.

[Route("api/[controller]")]
[ApiController]
public class UsersController : ControllerBase
{
// GET /api/users - Retrieves all users
[HttpGet]
public ActionResult GetUsers()
{
return Ok(new { data = Users }); // data = Users
}
}

Example Of a Not Best Practice Payload

[
{
"id": "1",
"name": "User One",
"email": "user1@example.com"
},
{
"id": "2",
"name": "User Two",
"email": "user2@example.com"
}
]

Example Of a Well-Structured Response Payload That Follows This Practice

{
"data": [
{
"id": "1",
"name": "User One",
"email": "user1@example.com"
},
{
"id": "2",
"name": "User Two",
"email": "user2@example.com"
}
],
"totalCount": 2,
"page": 1,
"pageSize": 10
}

4. Use Strings for Object Identifiers
Even if your database stores numeric values, object identifiers in your API responses should be strings. For instance, { "id": "456" } is preferable to { "id": 456 }. String IDs offer greater flexibility and adaptability for future development.

[Route("api/[controller]")]
[ApiController]
public class UsersController : ControllerBase
{
// GET /api/users/{id} - Retrieves a user by their ID
[HttpGet("{id}")]
public ActionResult GetUser(string id)
{
if (Users.TryGetValue(id, out var user))
{
return Ok(user);
}
return NotFound();
}
}

5. Proper Use of “404 Not Found”
While the HTTP specification states that 404 should be used when a resource is not found, this can be confusing. When a client requests GET /users/{user_id} for a nonexistent user, the response should clearly indicate two things: the server understood the request, but the user was not found. A 404 error might not effectively convey this, as it could be caused by various issues such as a misconfigured client, bad proxy settings, or load balancer problems.

[Route("api/[controller]")]
[ApiController]
public class UsersController : ControllerBase
{
[HttpGet("{id}")]
public ActionResult GetUser(string id)
{
if (Users.TryGetValue(id, out var user))
{
return Ok(user);
}
return NotFound(new { message = "User not found", userId = id });
}
}

6. Implement a Structured Error Format
In a large system with many consumers, having a structured error format is crucial. This format should include fields such as:

  • Error message
  • Type of error
  • Cause (if applicable)
namespace MyApi.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class UsersController : ControllerBase
{
[HttpGet("{id}")]
public ActionResult GetUser(string id)
{
if (Users.TryGetValue(id, out var user))
{
return Ok(user);
}
return NotFound(new ErrorResponse
{
ErrorMessage = "User not found",
ErrorType = "NotFound",
Cause = $"User with ID {id} does not exist"
});
}
}

public class ErrorResponse
{
public string ErrorMessage { get; set; }
public string ErrorType { get; set; }
public string Cause { get; set; }
}
}

These examples provide basic implementations for each best practice. You can expand upon them to suit the specific requirements and logic of your API. Stay tuned for more tips in future articles.ס;

We may use cookies or any other tracking technologies when you visit our website, including any other media form, mobile website, or mobile application related or connected to help customize the Site and improve your experience. learn more

Allow