DEV Community

Cover image for 5 Must-Know Techniques to Boost API Performance
Odumosu Matthew
Odumosu Matthew

Posted on

5 Must-Know Techniques to Boost API Performance

In the realm of API development, optimizing performance is crucial for delivering fast and reliable services to users. Here are five essential techniques to enhance API performance, along with implementation insights and code examples.

1. Caching
Caching involves storing frequently accessed data in a temporary storage (cache) to reduce latency and improve response times. When data is requested, the API first checks the cache. If the data exists, it’s returned immediately; otherwise, it’s fetched from the database and stored in the cache for future requests.

Implementation Example using Redis Cache:

// Example using Redis cache in ASP.NET Core
public class ProductController : ControllerBase
{
    private readonly IProductService _productService;

    public ProductController(IProductService productService)
    {
        _productService = productService;
    }

    [HttpGet("{id}")]
    public async Task<IActionResult> GetProduct(int id)
    {
        var product = await _productService.GetProductAsync(id);
        if (product != null)
        {
            return Ok(product);
        }
        else
        {
            return NotFound();
        }
    }
}

public class ProductService : IProductService
{
    private readonly IProductRepository _productRepository;
    private readonly IDistributedCache _cache;

    public ProductService(IProductRepository productRepository, IDistributedCache cache)
    {
        _productRepository = productRepository;
        _cache = cache;
    }

    public async Task<ProductDto> GetProductAsync(int id)
    {
        string cacheKey = $"product_{id}";
        var cachedProduct = await _cache.GetAsync(cacheKey);
        if (cachedProduct != null)
        {
            return JsonConvert.DeserializeObject<ProductDto>(Encoding.UTF8.GetString(cachedProduct));
        }
        else
        {
            var product = await _productRepository.GetProductAsync(id);
            if (product != null)
            {
                var options = new DistributedCacheEntryOptions
                {
                    AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(10),
                    SlidingExpiration = TimeSpan.FromMinutes(5)
                };
                await _cache.SetAsync(cacheKey, Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(product)), options);
                return product; // Assuming ProductRepository returns ProductDto
            }
            else
            {
                return null;
            }
        }
    }
}

Enter fullscreen mode Exit fullscreen mode

2. Scale-out with Load Balancing and API Gateways
Scaling your API involves distributing incoming requests across multiple server instances to handle increased traffic and improve reliability. Load balancers play a critical role in this process by evenly distributing requests to each server instance. API Gateways like Ocelot or YARP (Yet Another Reverse Proxy) provide additional functionality by serving as entry points for all client requests, allowing for routing, authentication, load balancing, and other cross-cutting concerns.

Implementation Consideration:

  • Ensure your API is stateless to benefit fully from horizontal scaling with load balancers and API gateways(Ocelot , YARP).

3. Async Processing
Async processing allows an API server to handle requests more efficiently by freeing up resources while waiting for time-consuming operations to complete. Clients are notified that their request is received and will be processed asynchronously.

Implementation Example:

// Example using async/await in ASP.NET Core
public async Task<IActionResult> ProcessOrderAsync(OrderDto order)
{
    // Process order asynchronously
    var result = await _orderProcessingService.ProcessOrderAsync(order);

    // Return response to client
    return Ok(result);
}

Enter fullscreen mode Exit fullscreen mode

4. Pagination
When an API endpoint returns a large dataset, pagination breaks the results into smaller, manageable chunks. This reduces response times and prevents overwhelming clients with excessive data. Pagination logic should ideally reside within the service or repository layers rather than in the API controllers.

Implementation Example:

// Example of pagination in ProductService.cs
public async Task<List<ProductDto>> GetProductsAsync(int page, int pageSize)
{
    var products = await _productRepository.GetProductsAsync();
    var paginatedProducts = products.Skip((page - 1) * pageSize).Take(pageSize).ToList();
    return paginatedProducts;
}

Enter fullscreen mode Exit fullscreen mode

5. Connection Pooling
Establishing a new database connection for every API request can be inefficient and impact performance. Connection pooling maintains a pool of reusable database connections, minimizing overhead and improving response times in high-concurrency scenarios.

Implementation Example:

// Example using SqlConnection and connection pooling in ASP.NET Core
public async Task<IActionResult> GetCustomers()
{
    using (var connection = new SqlConnection(connectionString))
    {
        await connection.OpenAsync();
        var command = new SqlCommand("SELECT * FROM Customers", connection);
        var reader = await command.ExecuteReaderAsync();
        var customers = new List<Customer>();
        while (await reader.ReadAsync())
        {
            var customer = new Customer
            {
                Id = reader.GetInt32(0),
                Name = reader.GetString(1)
                // Populate other properties
            };
            customers.Add(customer);
        }
        return Ok(customers);
    }
}

Enter fullscreen mode Exit fullscreen mode

Conclusion
By implementing these five techniques—caching with Redis, utilizing load balancing and API gateways like Ocelot or YARP, async processing, pagination within the service layer, and connection pooling—you can significantly enhance the performance and scalability of your API. Each technique addresses specific challenges and optimizations, contributing to a faster, more efficient API that meets the demands of modern applications.

Start applying these strategies in your API development to deliver better user experiences and optimize resource utilization effectively. Happy coding!

LinkedIn Account : LinkedIn
Twitter Account: Twitter
Credit: Graphics sourced from Medium

Top comments (1)

Collapse
 
aregaz profile image
Illia Ratkevych

Nice list! I would recommend almost everything the same but in a different order.

API caching is obviously #1 since it can be done outside of the API application with literally zero code changes.
But my #2 would be paging (or any other means to reduce the amount of data to return).
I would put async approach on #3 - but actually I do async versions by default nowadays, so this is actually #0.
I would put a load balancer on #4 since the complexity of setting up an LB might be far more advanced than the first three methods.
But instead of DB connection pooling (which I'm sure you also need to do by default—especially with Entity Framework), I would suggest adding another caching layer over your database. Of course, only if you measured your requests' performance and figured out that the database is a bottleneck. If your application is doing "more reads of the same data" rather than "every read request is unique," you can wrap the database response in a cache (like Redis). It can improve performance drastically in some scenarios.