DEV Community

Cover image for TOP 15 Mistakes Developers Make When Creating Web APIs
Anton Martyniuk
Anton Martyniuk

Posted on • Originally published at antondevtips.com on

TOP 15 Mistakes Developers Make When Creating Web APIs

In this blog post, I'll walk you through the top 15 mistakes developers often make when building Web APIs in .NET.
I will show you solutions to avoid and fix these mistakes.

Let's dive in!

On my website: antondevtips.com I share .NET and Architecture best practices.
Subscribe to become a better developer.

1. Not Using API Versioning

Mistake:
Without versioning, changes to your API can break existing client applications that rely on older endpoints.

var builder = WebApplication.CreateBuilder(args);

var app = builder.Build();

app.MapGet("/api/products", async (IProductService service) =>
{
    var products = await service.GetProductsAsync();
    return Results.Ok(products);
});

app.Run();
Enter fullscreen mode Exit fullscreen mode

Solution:
Implement API versioning using URL segments, query parameters, or headers to ensure backward compatibility and smooth transitions between versions.

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddApiVersioning(options =>
{
    options.DefaultApiVersion = new ApiVersion(1,0);
    options.AssumeDefaultVersionWhenUnspecified = true;
});

var app = builder.Build();

app.MapGet("/api/v1/products", async (IProductService service) =>
{
    var products = await service.GetProductsAsync();
    return Results.Ok(products);
});

app.Run();
Enter fullscreen mode Exit fullscreen mode

2. Poor Error Handling

Mistake:
Poor error messages make it tough for API consumers to diagnose what went wrong.

app.MapGet("/api/v1/order/{id}", (int id) =>
{
    if (id < 0)
    {
        return Results.Problem();
    }

    return Results.Ok(order);
});
Enter fullscreen mode Exit fullscreen mode

Solution:
Use standardized error responses like ProblemDetails and appropriate HTTP status codes to help clients understand and resolve problems.

app.MapGet("/api/v1/order/{id}", (int id) =>
{
    if (id < 0)
    {
        return Results.Problem(
            detail: "The order ID provided is invalid.",
            statusCode: StatusCodes.Status400BadRequest,
            title: "Invalid Request"
        );
    }

    return Results.Ok(order);
});
Enter fullscreen mode Exit fullscreen mode

3. Lack of Authentication and Authorization

Mistake:
Failing to secure your API exposes it to unauthorized access and potential data breaches.

// Program.cs - Mistake (no authentication setup, open endpoints)
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

// Anyone can call this endpoint without any credentials
app.MapGet("/api/orders", async (IOrder service) =>
{
    var orders = await service.GetOrdersAsync();
    return Results.Ok(orders);
});

app.Run();
Enter fullscreen mode Exit fullscreen mode

Solution:
Implement robust authentication and authorization mechanisms, such as OAuth 2.0 or JWT, to protect your API endpoints.

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddJwtBearer(options =>
    {
        options.TokenValidationParameters = new TokenValidationParameters
        {
            ValidateIssuer = true,
            ValidateAudience = true,
            ValidateIssuerSigningKey = true,
            IssuerSigningKey = new SymmetricSecurityKey(
                Encoding.UTF8.GetBytes("your-secret-key"))
        };
    });

var app = builder.Build();

app.UseAuthentication();
app.UseAuthorization();

app.MapGet("/api/orders", async (IOrder service) =>
{
    var orders = await service.GetOrdersAsync();
    return Results.Ok(orders);
}).RequireAuthorization();

app.Run();
Enter fullscreen mode Exit fullscreen mode

4. Ignoring Asynchronous Programming

Mistake:
Synchronous code can lead to thread blocking and preventing them from handling other requests.
This reduces performance significantly.

app.MapGet("/api/v1/products", (IProductService productService) =>
{
    var products = productService.GetProducts();
    return Ok(products);
});
Enter fullscreen mode Exit fullscreen mode

Solution:
Use async and await keywords paired with asynchronous methods, improving scalability of web server.

Asynchronous programming prevents your application from blocking threads while waiting for I/O operations to complete.
Among these operations: reading files, waiting for database results, waiting for API call results, etc.

app.MapGet("/api/v1/products", async (IProductService productService) =>
{
    var products = await productService.GetProductsAsync();
    return Ok(products);
});
Enter fullscreen mode Exit fullscreen mode

5. Not Following RESTful Conventions

Mistake:
Ignoring RESTful principles can lead to inconsistent and non-standard APIs.

// Not following REST: using GET for deletion
app.MapGet("/api/deleteUser?id=123", () =>
{
    // Delete logic here
    return Results.Ok("Deleted");
});
Enter fullscreen mode Exit fullscreen mode

Solution:
Design your API following RESTful conventions, using appropriate HTTP methods (GET, POST, PUT, DELETE, etc.) and status codes.

app.MapGet("/api/users/{id}", (int id) =>
{
    var user = ...;
    return Results.Ok(user);
});

app.MapPost("/api/users", (CreateUserRequest request) =>
{
    var user = request.MapToEntity();
    return Results.Created(user);
});

app.MapPut("/api/users/{id}", (int id, UpdateUserRequest request) =>
{
    // Update User
    return Results.NoContent();
});

app.MapDelete("/api/users/{id}", (int id) =>
{
    // Delete User    
    return Results.NoContent();
});
Enter fullscreen mode Exit fullscreen mode

6. Not Validating Input Data

Mistake:
Accepting unvalidated input can lead to security vulnerabilities and data integrity issues.

app.MapPost("/api/users", (CreateUserRequest request) =>
{
    // No validation performed
    return Results.Ok(user);
});
Enter fullscreen mode Exit fullscreen mode

Solution:
Validate all incoming data using Data Annotations or Fluent Validation.
Always be pessimistic about data your APIs consume.

app.MapPost("/api/users", (CreateUserRequest request,
    IValidator<CreateUserRequest> validator) =>
{
    var validationResult = await validator.ValidateAsync(request);
    if (!validationResult.IsValid)
    {
        return Results.ValidationProblem(validationResult.ToDictionary());
    }

    return Results.Ok(user);
});
Enter fullscreen mode Exit fullscreen mode

7. Ignoring Security Best Practices

Mistake:
Ignoring security measures can expose your API to attacks like SQL injection or cross-site scripting.

app.MapGet("/api/product/{id}", (string name) =>
{
    // Vulnerable SQL string concatenation
    var command = new SqlCommand(
        "SELECT * FROM Products WHERE Name = " + name, connection);

    connection.Open();
    using var reader = command.ExecuteReader();
    // ...
    return Results.Ok();
});
Enter fullscreen mode Exit fullscreen mode

This API endpoint is vulnerable to SQL injection, as name parameter can accept any string like an SQL command.

Solution:
Follow security best practices, such as using parameterized queries, encrypting sensitive data, and enforcing HTTPS.

// Use safe methods from ORM like EF Core or Dapper
// Or use parameters to prevent SQL injection
app.MapGet("/api/product/{id}", (string name, ProductDbContext dbContext) =>
{
    var products = await dbContext.Products
        .Where(x => x.Name === name)
        .ToListAsync();

    return Results.Ok(products);
});
Enter fullscreen mode Exit fullscreen mode

8. Poor Logging and Monitoring

Mistake:
Without proper logging, diagnosing issues becomes challenging, especially in distributed systems.

Solution:
Add Open Telemetry and Logging to your application to track application metrics, traces and logs.
Use tools like Serilog for logging and Seq or Jaeger to view distributed traces.
You can use Seq both for logs and distributed traces.

Consider these free tools for monitoring:

  • Jaeger (distributed traces)
  • Loki (logs)
  • Prometheus and Grafana (metrics)
  • Seq (logs and distributed traces) (free only for a single user)
  • .NET Aspire (logging, metrics, distributed traces)

You can also use cloud-based solutions for monitoring your applications like Application Insights.

builder.Services
    .AddOpenTelemetry()
    .ConfigureResource(resource => resource.AddService("ShippingService"))
    .WithTracing(tracing =>
    {
        tracing
            .AddAspNetCoreInstrumentation()
            .AddHttpClientInstrumentation()
            .AddEntityFrameworkCoreInstrumentation()
            .AddRedisInstrumentation()
            .AddNpgsql();

        tracing.AddOtlpExporter();
    });
Enter fullscreen mode Exit fullscreen mode

9. Lack of API Documentation

Mistake:
Without clear documentation, clients struggle to understand how to use your API.

Solution:
Provide thorough documentation using tools like Swagger/OpenAPI to generate interactive API docs.

builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen(c =>
{
    c.SwaggerDoc("v1", new OpenApiInfo { Title = "My API", Version = "v1" });
});

var app = builder.Build();

if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

app.MapGet("/api/products", () => new[] { "Product1", "Product2" });

app.Run();
Enter fullscreen mode Exit fullscreen mode

10. Not Optimizing Database and Data Access Layer

Mistake:
Unoptimized database queries and inefficient data access code can lead to slow performance and scalability issues.

// Fetching ALL columns
var book = await context.Books
    .Include(b => b.Author)
    .FirstOrDefaultAsync(b => b.Id == id, cancellationToken);
Enter fullscreen mode Exit fullscreen mode

Solution:
Optimize your database by indexing, optimizing queries, and using efficient data access patterns in EF Core or Dapper.

// Fetching only needed columns without tracking
var book = await context.Books
    .Include(b => b.Author)
    .Where(b => b.Id == id)
    .Select(b => new BooksPreviewResponse
    {
        Title = b.Title, Author = b.Author.Name, Year = b.Year
    })
    .FirstOrDefaultAsync(cancellationToken);
Enter fullscreen mode Exit fullscreen mode

11. Returning Too Much Data Without Paging, Filtering, or Sorting

Mistake:
Sending large datasets can lead to performance bottlenecks and increased bandwidth usage.

// Selecting all books (entire database)
var allBooks = await context.Books
    .Include(b => b.Author)
    .ToListAsync();
Enter fullscreen mode Exit fullscreen mode

Solution:
Implement paging, filtering, and sorting mechanisms to allow clients to retrieve only the data they need.

// Use paging to select fixed number of records
int pageSize = 50;
int pageNumber = 1;

var books = context.Books
    .AsNoTracking()
    .OrderBy(p => p.Title)
    .Skip((pageNumber - 1) * pageSize)
    .Take(pageSize)
    .ToList();
Enter fullscreen mode Exit fullscreen mode

12. Not Using Caching

Mistake:
Not using cache for frequently accessed data can result in unnecessary database hits and slower response times.

Solution:
Implement caching strategies using in-memory caches like MemoryCache/HybridCache (.NET 9) or distributed caches like Redis to improve performance.

builder.Services.AddHybridCache();

[HttpGet("orders/{id}")]
public async Task<IActionResult> GetOrderAsync(int id,
    [FromServices] IHybridCache cache)
{
    string cacheKey = $"Order_{id}";
    var order = await cache.GetOrCreateAsync(cacheKey, async entry =>
    {
        entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(10);
        using var context = new AppDbContext();
        return await context.Orders.FindAsync(id);
    });

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

    return Ok(order);
}
Enter fullscreen mode Exit fullscreen mode

13. Returning Big Payloads Without Compression

Mistake:
Large uncompressed responses consume more bandwidth and can slow down data transfer rates.

Solution:
Compressing your responses with Brotli or GZIP can significantly reduce payload size.
Smaller responses mean faster data transfer and a better user experience.

Brotli and Gzip reduce the size of the outgoing JSON, HTML or Static files data.
Adding them early in the pipeline will ensure smaller payloads.

builder.Services.AddResponseCompression(options =>
{
    options.EnableForHttps = true;
    options.Providers.Add<BrotliCompressionProvider>();
    options.Providers.Add<GzipCompressionProvider>();
});

builder.Services.Configure<BrotliCompressionProviderOptions>(options =>
{
    options.Level = System.IO.Compression.CompressionLevel.Fastest;
});

builder.Services.Configure<GzipCompressionProviderOptions>(options =>
{
    options.Level = System.IO.Compression.CompressionLevel.Fastest;
});

var app = builder.Build();
app.UseResponseCompression();
Enter fullscreen mode Exit fullscreen mode

14. Fat Controllers

Mistake:
Overloaded controllers with too much logic and methods become hard to maintain and test.

public class ProductsController(
    IProductRepository productRepository,
    ILoggingService loggingService,
    ICacheService cacheService,
    IEmailService emailService,
    IAuthenticationService authService,
    IReportGenerator reportGenerator,
    IFeatureFlagService featureFlagService
) : ControllerBase
{
    public IActionResult GetAllProducts() { }
    public IActionResult GetProductById(int id) { }
    public IActionResult CreateProduct() { }
    public IActionResult UpdateProduct(int id) { }
    public IActionResult DeleteProduct(int id) { }
    public IActionResult GetProductsByCategory(string category) { }
    public IActionResult ExportProducts()  { }
    public IActionResult SendProductNewsletter()  { }
    public IActionResult GetProductStats()  { }
    public IActionResult GetProductRecommendations() { }
}
Enter fullscreen mode Exit fullscreen mode

Solution:
Apply the Single Responsibility Principle by moving business logic into services or other layers.
Break big controllers to smaller ones - keeping them focused on one thing or entity.

// For example, break them into specialized controllers or Minimal APIs
// ProductController - focuses on product entity
public record Product(int Id, string Name);

[ApiController]
[Route("api/[controller]")]
public class ProductController : ControllerBase
{
    [HttpGet]
    public IActionResult GetAllProducts() 
    {
        // ...
    }

    [HttpGet("{id}")]
    public IActionResult GetProductById(int id) 
    {
        // ...
    }

    // ...
}
Enter fullscreen mode Exit fullscreen mode

15. Not Using Minimal APIs

Mistake:
Controllers can quickly become out of order with too much logic inside.

Solution:
Replace Controllers with Minimal APIs or Fast Endpoints creating a new class per endpoint.
This makes endpoints single responsible and not coupled with other endpoints.

Minimal APIs in .NET 9 received a huge performance boost and can process 15% more requests per second than in .NET 8.
Also, Minimal APIs consume 93% less memory compared to a previous version.

app.MapGet("/api/v1/products", async (IProductService service) =>
{
    var products = await service.GetProducts();
    return Results.Ok(products);
});

app.MapGet("/api/orders", async (IOrder service) =>
{
    var orders = await service.GetOrders();
    return Results.Ok(orders);
});
Enter fullscreen mode Exit fullscreen mode

Summary

By avoiding these 15 common pitfalls - your .NET Web APIs will be more reliable, secure, and scalable.

  • Keep your endpoints versioned for backward compatibility.
  • Offer meaningful error messages.
  • Secure your APIs with authentication and authorization.
  • Use async/await for better scalability and performance.
  • Stick to RESTful conventions.
  • Validate input, be pessimistic about data you receive.
  • Follow security best practices.
  • Log and monitor your applications.
  • Document APIs with Swagger/OpenAPI.
  • Optimize your data access.
  • Use paging/filtering/sorting for large datasets.
  • Cache often-used data.
  • Compress large responses.
  • Keep controllers small and lean.
  • Use Minimal APIs for speed and simplicity

On my website: antondevtips.com I share .NET and Architecture best practices.
Subscribe to become a better developer.

Top comments (7)

Collapse
 
boby900 profile image
Boby Tiwari

Nice read

Collapse
 
zakwillis profile image
zakwillis

Good article, and am most certainly not going to pick holes in it, this as pretty much the API on my platforms adheres to most of these and has a few extra additions.

Controllers should be lightweight hopefully, with services and other abstractions encapsulating the work.

If data needs to be compressed, better to compress it off website to reduce server load for example. I have this on my API where I pass zipped files to the server folders to be served by the API. This means the server isn't spending time compressing content.

Another thing - versioning, yes. This is cool if you are potentially providing backwards compatibility, but with my APIs my data structures are fixed but dynamic. Clients effectively discover the data within the structure and respond to it. This reduces the number of endpoints. For example, the good thing about versioning is you are telling clients that you intend to honour that data contract. By not versioning, you are telling clients it could change or disappear.

For example, querying this endpoint cryptostatto.com/api/entityreport with a Get returns this

[{"entityDisplayName":"Asset24HrMetrics","entityName":"Asset24-Hr-Metrics"},{"entityDisplayName":"AssetMetrics24HrsHourlyChart","entityName":"Asset-Metrics24-Hrs-Hourly-Chart"},{"entityDisplayName":"AssetMetricsHourlyChangeComparedLastToValue","entityName":"Asset-Metrics-Hourly-Change-Compared-Last-To-Value"},{"entityDisplayName":"AssetPercentageChangeRangePeriod","entityName":"Asset-Percentage-Change-Range-Period"},{"entityDisplayName":"AssetRateChangeMetric3Days","entityName":"Asset-Rate-Change-Metric3-Days"},{"entityDisplayName":"Assets","entityName":"Assets"}]
Enter fullscreen mode Exit fullscreen mode

Suffixing the entityName on the same endpoint returns this... Which the client can figure out from the structure how to work with it.

{"entityName":"Asset24-Hr-Metrics","reportVMs":[{"entityReportKey":"1e8d5645-ece3-4286-86c3-26e38ba2859b","reportName":"Top Assets Aggregate Price Hourly % movement last 24 Hrs","reportType":4,"reportOptions":[{"isDefault":false,"option":{"fieldName":"Component","values":["Asset"]}},{"isDefault":false,"option":{"fieldName":"MeasureName","values":["Price Usd","Usd Price"]}},{"isDefault":false,"option":{"fieldName":"ItemCode","values":["EOS","OKB","ANKR","WEMIX","KAVA","DEXE","THETA","CELO","XRP","GALA","FLOW","NEO","PENDLE","MINA","AAVE","1INCH","BITCOIN","NEXO","Movement Totals"]}},{"isDefault":false,"option":{"fieldName":"ItemName","values":["EOS","OKB","Ankr","WEMIX","Kava","DeXe","THETA","Theta Network","Celo","HarryPotterObamaPacMan8Inu","XRP","Gala","Flow","Neo","Pendle","Mina","Mina Protocol","Aave","1inch Network","HarryPotterObamaSonic10Inu (ETH)","Nexo","Direction"]}},{"isDefault":false,"option":{"fieldName":"Period","values":["Hour"]}},{"isDefault":false,"option":{"fieldName":"PeriodValue","values":["8","9"]}},{"isDefault":false,"option":{"fieldName":"MeasureType","values":["PeriodyRankPercentageIncrease","HourlyRankPercentageDecrease","TotalHourlyIncreases","TotalHourlyDecreases"]}},{"isDefault":false,"option":{"fieldName":"IsScaled","values":["True"]}}],"initialisedOn":"2025-02-14T10:08:47.152839Z","clientReportTypes":["Periodic Value Pivotable Data"]},{"entityReportKey":"e003a10e-fa76-4a18-baad-fcbea529eb62","reportName":"Top Asset Price Usd Last 24 Hrs breakdown","reportType":4,"reportOptions":[{"isDefault":false,"option":{"fieldName":"Component","values":["Asset"]}},{"isDefault":false,"option":{"fieldName":"MeasureName","values":["Price Usd"]}},{"isDefault":false,"option":{"fieldName":"ItemCode","values":["1INCH","AAVE","ANKR","BITCOIN","CELO","DEXE","EOS","FLOW","GALA","KAVA","MINA","NEO","NEXO","OKB","PENDLE","THETA","WEMIX","XRP"]}},{"isDefault":false,"option":{"fieldName":"ItemName","values":["1inch Network","Aave","Ankr","HarryPotterObamaSonic10Inu (ETH)","Celo","DeXe","EOS","Flow","Gala","Kava","Mina","Mina Protocol","Neo","Nexo","OKB","Pendle","THETA","Theta Network","WEMIX","HarryPotterObamaPacMan8Inu","XRP"]}},{"isDefault":false,"option":{"fieldName":"Period","values":["Overall Period","Hour"]}},{"isDefault":false,"option":{"fieldName":"PeriodValue","values":["-1","8","9","-2"]}},{"isDefault":false,"option":{"fieldName":"MeasureType","values":["Opening","Closing","LowestValue","HighestValue"]}},{"isDefault":false,"option":{"fieldName":"IsScaled","values":["False"]}}],"initialisedOn":"2025-02-14T10:08:47.0357559Z","clientReportTypes":["Periodic Value Pivotable Data"]},{"entityReportKey":"8d727e73-9160-49d3-b07f-99e3a47eae8c","reportName":"Open and close over 24 hrs in proximity to Period High","reportType":4,"reportOptions":[{"isDefault":false,"option":{"fieldName":"Component","values":["Asset"]}},{"isDefault":false,"option":{"fieldName":"MeasureName","values":["Price Usd","Usd Price"]}},{"isDefault":false,"option":{"fieldName":"ItemCode","values":["EOS","OKB","ANKR","WEMIX","KAVA","DEXE","THETA","CELO","XRP","GALA","FLOW","NEO","PENDLE","MINA","AAVE","1INCH","BITCOIN","NEXO","Movement Totals"]}},{"isDefault":false,"option":{"fieldName":"ItemName","values":["EOS","OKB","Ankr","WEMIX","Kava","DeXe","THETA","Theta Network","Celo","HarryPotterObamaPacMan8Inu","XRP","Gala","Flow","Neo","Pendle","Mina","Mina Protocol","Aave","1inch Network","HarryPotterObamaSonic10Inu (ETH)","Nexo","Direction"]}},{"isDefault":false,"option":{"fieldName":"Period","values":["Hour","Overall Period"]}},{"isDefault":false,"option":{"fieldName":"PeriodValue","values":["8","9","-1"]}},{"isDefault":false,"option":{"fieldName":"MeasureType","values":["PeriodyRankPercentageIncrease","HourlyRankPercentageDecrease","TotalHourlyIncreases","TotalHourlyDecreases","OpeningPositionOnScale","ClosingPositionOnScale"]}},{"isDefault":false,"option":{"fieldName":"IsScaled","values":["True"]}}],"initialisedOn":"2025-02-14T10:08:46.8492996Z","clientReportTypes":["Periodic Value Pivotable Data"]},{"entityReportKey":"8c0c9213-e8af-461f-9850-99430486816e","reportName":"Current Top Assets USD 24 Hr Time frame","reportType":4,"reportOptions":[{"isDefault":false,"option":{"fieldName":"ordinal","values":["1","2","3","4","5","6","7","8","9","10","11","12","13","14","15","16","17","18","19","20","21","22","23","24","25"]}},{"isDefault":false,"option":{"fieldName":"CurrentDate","values":["04/24/2024 12:32:53","04/24/2024 11:32:53","04/24/2024 10:32:53","04/24/2024 09:32:53","04/24/2024 08:32:53","04/24/2024 07:32:53","04/24/2024 06:32:53","04/24/2024 05:32:53","04/24/2024 04:32:53","04/24/2024 03:32:53","04/24/2024 02:32:53","04/24/2024 01:32:53","04/24/2024 00:32:53","04/23/2024 23:32:53","04/23/2024 22:32:53","04/23/2024 21:32:53","04/23/2024 20:32:53","04/23/2024 19:32:53","04/23/2024 18:32:53","04/23/2024 17:32:53","04/23/2024 16:32:53","04/23/2024 15:32:53","04/23/2024 14:32:53","04/23/2024 13:32:53","04/23/2024 12:32:53"]}},{"isDefault":false,"option":{"fieldName":"Hour","values":["12","11","10","9","8","7","6","5","4","3","2","1","0","23","22","21","20","19","18","17","16","15","14","13"]}},{"isDefault":false,"option":{"fieldName":"HourStart","values":["04/24/2024 12:00:00","04/24/2024 11:00:00","04/24/2024 10:00:00","04/24/2024 09:00:00","04/24/2024 08:00:00","04/24/2024 07:00:00","04/24/2024 06:00:00","04/24/2024 05:00:00","04/24/2024 04:00:00","04/24/2024 03:00:00","04/24/2024 02:00:00","04/24/2024 01:00:00","04/24/2024 00:00:00","04/23/2024 23:00:00","04/23/2024 22:00:00","04/23/2024 21:00:00","04/23/2024 20:00:00","04/23/2024 19:00:00","04/23/2024 18:00:00","04/23/2024 17:00:00","04/23/2024 16:00:00","04/23/2024 15:00:00","04/23/2024 14:00:00","04/23/2024 13:00:00","04/23/2024 12:00:00"]}},{"isDefault":false,"option":{"fieldName":"HourEnd","values":["04/24/2024 12:59:59","04/24/2024 11:59:59","04/24/2024 10:59:59","04/24/2024 09:59:59","04/24/2024 08:59:59","04/24/2024 07:59:59","04/24/2024 06:59:59","04/24/2024 05:59:59","04/24/2024 04:59:59","04/24/2024 03:59:59","04/24/2024 02:59:59","04/24/2024 01:59:59","04/24/2024 00:59:59","04/23/2024 23:59:59","04/23/2024 22:59:59","04/23/2024 21:59:59","04/23/2024 20:59:59","04/23/2024 19:59:59","04/23/2024 18:59:59","04/23/2024 17:59:59","04/23/2024 16:59:59","04/23/2024 15:59:59","04/23/2024 14:59:59","04/23/2024 13:59:59","04/23/2024 12:59:59"]}}],"initialisedOn":"2025-02-14T10:08:46.6631566Z","clientReportTypes":[]},{"entityReportKey":"10b9954e-e3bd-4112-a731-8da1b59c5232","reportName":"Asset Price Hourly % movement rankings Last 24 Hrs","reportType":4,"reportOptions":[{"isDefault":false,"option":{"fieldName":"Component","values":["Asset"]}},{"isDefault":false,"option":{"fieldName":"MeasureName","values":["Price Usd"]}},{"isDefault":false,"option":{"fieldName":"ItemCode","values":["EOS","OKB","ANKR","WEMIX","KAVA","DEXE","THETA","CELO","XRP","GALA","FLOW","NEO","PENDLE","MINA","AAVE","1INCH","BITCOIN","NEXO"]}},{"isDefault":false,"option":{"fieldName":"ItemName","values":["EOS","OKB","Ankr","WEMIX","Kava","DeXe","THETA","Theta Network","Celo","HarryPotterObamaPacMan8Inu","XRP","Gala","Flow","Neo","Pendle","Mina","Mina Protocol","Aave","1inch Network","HarryPotterObamaSonic10Inu (ETH)","Nexo"]}},{"isDefault":false,"option":{"fieldName":"Period","values":["Hour"]}},{"isDefault":false,"option":{"fieldName":"PeriodValue","values":["8","9"]}},{"isDefault":false,"option":{"fieldName":"MeasureType","values":["PeriodyRankPercentageIncrease","HourlyRankPercentageDecrease"]}},{"isDefault":false,"option":{"fieldName":"IsScaled","values":["True"]}}],"initialisedOn":"2025-02-14T10:08:46.1070902Z","clientReportTypes":["Periodic Value Pivotable Data"]}]}
Enter fullscreen mode Exit fullscreen mode

Another big thing about REST is that if people read Roy T Fielding's paper on Rest - the idea is that clients on APIs should be able to discover content.

Collapse
 
devh0us3 profile image
Alex P

Ah, some more:

app.MapGet("/api/v1/order/{id}", (int id) =>

Who use integer as type for id today?

You must use guid! It prevents any enumeration attack, even if you forgot to add authorisaztion to your code - attacker cannot guess any other ID and least because there are 2^128 options (actually less, but for simple explain)

You must avoid id for any arguments of your API!

Collapse
 
devh0us3 profile image
Alex P

Hello, you suggest

    .Skip((pageNumber - 1) * pageSize)
    .Take(pageSize)
Enter fullscreen mode Exit fullscreen mode

But it's the worst solution, better:

SELECT col1, col2, col3, datetime, id 
FROM mytable
WHERE 
(datetime, id) < ('2022-12-27 08:26:49.219717', '059b39dc-957f-4ef6-907a-f835891bbd36') 
ORDER BY datetime, id
limit 10;
Enter fullscreen mode Exit fullscreen mode

just imaging how many records should your database read in your case and my if a user go to the page number 1_000_000? just to take top 10 records your database should filter out 1_000_000 records, it's useless operations

and some more: .OrderBy(p => p.Title) - must include additional ordering by ID of entity, at least because items may contains the same names

I have tried to find a video for you, just watch https://www.youtube.com/watch?v=GzMaN-IX7wQ&list=PLKBbrjINK98eTr6YUG97vT29Bv418gdrc&index=13

Yeah, paging it's a must have practice, but using limit offset – it's a worst practice

Collapse
 
mahmoudalaskalany profile image
Mahmoud Alaskalany

Thanks for this checklist ✅
I can confirm my self i did a mistake in ali convention here but thanks to version i managed to fix it in v2

Collapse
 
syedmuhammadaliraza profile image
Syed Muhammad Ali Raza

👍

Collapse
 
0xm4r5h4l profile image
Binjamin Smoker

Nice article, and don't forget: Lack of security headers, Lack of rate-limit