DEV Community

Adrián Bailador
Adrián Bailador

Posted on

3

Top 12 Best Practices for REST APIs using C# .NET

Introduction

Designing a good RESTful API remains a crucial part of modern application development. With the rise of mobile applications, distributed architectures, and microservices, creating APIs that are easy to use, efficient, and secure is more important than ever. This article will explore 12 best practices for building REST APIs, focusing on C# .NET.

1. Proper Use of HTTP Methods

Each HTTP method has a specific purpose:

  • GET: Retrieve information without causing side effects.
  • POST: Create new resources.
  • PUT: Update existing resources or create them if they don’t exist.
  • DELETE: Remove resources.
  • PATCH: Apply partial updates to a resource.

Example in C# .NET:

[HttpGet("{id}")]
public IActionResult GetUser(int id)
{
    var user = _userService.GetUserById(id);
    if (user == null)
    {
        return NotFound();
    }
    return Ok(user);
}
Enter fullscreen mode Exit fullscreen mode

2. Use Clear Resource Naming Conventions

To make your API routes easy to understand:

  • Use plural nouns for collections (/users).
  • Use singular nouns for individual items (/users/{id}).
  • Avoid using verbs in routes (e.g., instead of /getUser, use /users/{id}).

Recommended Link: Best Practices for RESTful API Design

3. Keep Your API Stateless

Each request sent to the server should contain all the information necessary to process it. Avoid relying on server-side state between requests. If you need to maintain state, use tokens like JWT or unique identifiers sent from the client.

4. Use Appropriate HTTP Status Codes

HTTP status codes help clients understand what happened with their request:

  • 200 OK: The request was successful.
  • 201 Created: A resource was successfully created.
  • 400 Bad Request: The request contains errors.
  • 404 Not Found: The resource was not found.
  • 500 Internal Server Error: Something went wrong on the server.

Example in C# .NET:

[HttpPost]
public IActionResult CreateUser([FromBody] UserDto userDto)
{
    if (!ModelState.IsValid)
    {
        return BadRequest(ModelState);
    }
    var createdUser = _userService.CreateUser(userDto);
    return CreatedAtAction(nameof(GetUser), new { id = createdUser.Id }, createdUser);
}
Enter fullscreen mode Exit fullscreen mode

5. Let the Client Choose the Response Format

Allow clients to specify the response format using the Accept header.

  • JSON: The most popular format.
  • XML: For older systems that still require it.

In ASP.NET Core, you can configure this as follows:

services.AddControllers(options => options.RespectBrowserAcceptHeader = true)
        .AddXmlSerializerFormatters();
Enter fullscreen mode Exit fullscreen mode

6. Implement Versioning

Versioning helps maintain backward compatibility when making changes to your API:

  • Use paths like /api/v1/users.
  • Use query strings like /api/users?version=1.
  • Use HTTP headers like Accept: application/vnd.myapi.v1+json.

Learn more: API Versioning in ASP.NET Core

7. Keep Your API Secure

Ensure your API is protected with authentication and authorisation:

  • OAuth2 or JWT for secure access.
  • Configure CORS to control who can access your API.

Example in C# .NET (JWT):

services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddJwtBearer(options =>
    {
        options.TokenValidationParameters = new TokenValidationParameters
        {
            ValidateIssuer = true,
            ValidateAudience = true,
            ValidateLifetime = true,
            ValidateIssuerSigningKey = true,
            ValidIssuer = Configuration["Jwt:Issuer"],
            ValidAudience = Configuration["Jwt:Audience"],
            IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["Jwt:Key"]))
        };
    });
Enter fullscreen mode Exit fullscreen mode

Configuring CORS is also essential:

services.AddCors(options =>
{
    options.AddDefaultPolicy(builder =>
    {
        builder.WithOrigins("https://example.com")
               .AllowAnyHeader()
               .AllowAnyMethod();
    });
});
Enter fullscreen mode Exit fullscreen mode

8. Handle Errors Clearly

Provide meaningful error messages that help developers understand what went wrong:

  • Use a consistent format for error messages.
  • Include helpful information like codes and clear descriptions.

Example in C# .NET:

app.UseExceptionHandler("/error");

[Route("/error")]
public IActionResult HandleError()
{
    var context = HttpContext.Features.Get<IExceptionHandlerFeature>();
    var exception = context?.Error;
    var problemDetails = new ProblemDetails
    {
        Status = 500,
        Title = "An error occurred while processing your request",
        Detail = exception?.Message
    };
    return StatusCode(500, problemDetails);
}
Enter fullscreen mode Exit fullscreen mode

9. Improve Performance with Caching

Caching can reduce server load and speed up responses for clients:

  • Use HTTP headers like Cache-Control and ETag.

Example in C# .NET:

[ResponseCache(Duration = 60, Location = ResponseCacheLocation.Client)]
public IActionResult GetCachedData()
{
    return Ok(_dataService.GetData());
}
Enter fullscreen mode Exit fullscreen mode

10. Implement Rate Limiting

To prevent abuse, implement rate limiting:

  • Return a 429 Too Many Requests code when the limit is exceeded.
  • Provide information about the limits in response headers.

Learn more: Rate Limiting in ASP.NET Core

11. Document Your API with Swagger

Swagger allows you to create interactive documentation that makes it easier for others to understand and use your API:

  • Automatically generate documentation from your controllers and models.

Example in C# .NET:

services.AddSwaggerGen(c =>
{
    c.SwaggerDoc("v1", new OpenApiInfo { Title = "Codú API", Version = "v1" });
});
Enter fullscreen mode Exit fullscreen mode

12. Test Your API

Testing is key to ensuring your API works as expected:

  • Write unit tests for controllers and services.
  • Implement integration tests to validate the entire workflow.

Example in C# .NET (Unit Testing):

[Fact]
public void GetUser_ShouldReturnOkResult()
{
    var controller = new UsersController(_userService);
    var result = controller.GetUser(1);
    Assert.IsType<OkObjectResult>(result);
}
Enter fullscreen mode Exit fullscreen mode

Sentry blog image

How to reduce TTFB

In the past few years in the web dev world, we’ve seen a significant push towards rendering our websites on the server. Doing so is better for SEO and performs better on low-powered devices, but one thing we had to sacrifice is TTFB.

In this article, we’ll see how we can identify what makes our TTFB high so we can fix it.

Read more

Top comments (2)

Collapse
 
stevsharp profile image
Spyros Ponaris

Great post! I’d like to add another recommendation: consider implementing HATEOAS (Hypermedia As The Engine Of Application State).

HATEOAS allows your API to dynamically guide clients by embedding hyperlinks within responses. This design pattern enhances discoverability, making APIs more intuitive, self-documenting, and easier to consume without relying heavily on external documentation.

For example, a response for fetching a user resource might look like this:

{
  "id": 1,
  "name": "John Doe",
  "email": "john.doe@example.com",
  "links": [
    { "rel": "self", "href": "/users/1", "method": "GET" },
    { "rel": "update", "href": "/users/1", "method": "PUT" },
    { "rel": "delete", "href": "/users/1", "method": "DELETE" }
  ]
}

Enter fullscreen mode Exit fullscreen mode

You can leverage libraries like RiskFirst.HATEOAS
github.com/riskfirst/riskfirst.hat...
to simplify HATEOAS implementation in your C# .NET projects.

Collapse
 
adrianbailador profile image
Adrián Bailador

Thank you very much, I will keep this in mind

Image of Timescale

Timescale – the developer's data platform for modern apps, built on PostgreSQL

Timescale Cloud is PostgreSQL optimized for speed, scale, and performance. Over 3 million IoT, AI, crypto, and dev tool apps are powered by Timescale. Try it free today! No credit card required.

Try free

👋 Kindness is contagious

Explore a sea of insights with this enlightening post, highly esteemed within the nurturing DEV Community. Coders of all stripes are invited to participate and contribute to our shared knowledge.

Expressing gratitude with a simple "thank you" can make a big impact. Leave your thanks in the comments!

On DEV, exchanging ideas smooths our way and strengthens our community bonds. Found this useful? A quick note of thanks to the author can mean a lot.

Okay