Skip to main content

25. Compression

About this chapter

In this chapter, we'll take a look at how we can enable compression for our API responses, but more importantly examine the scenarios and trade-offs that will help us to decide whether it is worth implementing in the first place.

compression in production

This chapter on Compression, along with the next on Rate Limiting, are 2 of the 4 Cross-cutting Concerns that we are looking at in Part 11, (the other 2 being: Health Checks and CORS).

One of the aims of this book is to provide guidance on building a production-quality API, and based on that aim, I wouldn't implement the .NET Middleware-based compression solution detailed in this chapter to satisfy that aim.

There isn't anything inherently wrong with the .NET Middleware approach, it's just that the compression solutions provided by most industry-grade hosting platforms (IIS, Apache, Nginx etc.) are more performant, and therefore suited to production scenarios than the one provided by .NET Middleware.

Microsoft gives this guidance too.

The reason I'm covering the .NET Middleware compression approach is because this is a book focused on .NET, so I think it makes sense to focus on what that ecosystem provides. It also allows us to discuss more general considerations related to compression.

Finally, I did also mention Rate Limiting at the start of this discussion. Same rules apply here too. I wouldn't implement Rate Limiting via .NET Middleware for production grade apps, instead opting to leverage a solution more dedicated to that feature set, e.g. API Gateways, CDN/WAF solutions etc.

Learning outcomes:

  • Understand what response compression is and how it reduces payload sizes
  • Evaluate the CPU vs bandwidth tradeoffs when deciding whether to implement compression
  • Identify scenarios where compression benefits (large payloads, external APIs, slow networks) vs where it doesn't (small responses, internal APIs, fast networks)
  • Understand the difference between .NET middleware compression and server-based compression (IIS, Nginx, Apache) and why server-based is preferred for production
  • Implement response compression middleware in ASP.NET Core with Gzip and Brotli providers
  • Position compression middleware correctly in the request pipeline
  • Understand how Accept-Encoding headers enable content negotiation between client and server
  • Test compression using REST clients and verify compression via response headers

Architecture Checkpoint

While we are making code changes in this chapter, they do not directly relate to the solution architecture. The code changes all relate to the request pipeline configured in Program.cs


Companion Code
  • The code for this section can be found here on GitHub
  • The complete finished code can be found here on GitHub

Feature branch

Ensure that main is current, then create a feature branch called: chapter_25_compression, and check it out:

git branch chapter_25_compression
git checkout chapter_25_compression
tip

If you can't remember the full workflow, refer back to Chapter 5

Compression

What is Response Compression?

Response compression is the process of reducing the size of HTTP responses before they're sent over the network. When enabled, the response body is compressed using algorithms like Gzip or Brotli, and the client (typically a browser or HTTP client) decompresses it upon receipt.

For text-based responses like JSON, HTML, or XML, compression can reduce payload sizes by 60-80% or more. For example, a 100KB JSON response might compress down to 20-30KB, significantly reducing the amount of data transmitted over the network.

Why Use Compression?

The primary benefit of response compression is bandwidth reduction. This translates to:

  • Faster response times for clients on slower networks (mobile, remote locations)
  • Reduced bandwidth costs for both server and client
  • Improved user experience particularly for users with limited data plans
  • Better performance for large response payloads

The Tradeoffs

While compression sounds universally beneficial, it's not appropriate for every scenario. There are important tradeoffs to consider:

  • CPU vs bandwidth: Compression uses CPU but saves bandwidth, this is arguably the primary trade-off to consider
  • Network conditions: On high latency/low bandwidth networks, compression helps a lot
  • Payload Size: Small responses (<1KB), the overhead may not be worth it
  • Internal vs external:
    • Internal APIs on fast networks (same datacenter) probably not worth it
    • External APIs over internet, usually beneficial

Example scenarios

ScenarioRecommendation
Microservices in the same clusterSkip compression
API Gateway & backend APIConsider if responses are large
Public API for external consumersEnable it
High latency connectionsEnable it
Large JSON payloadsEnable it

Compression in .NET

Microsoft provides compression middleware (that we'll implement in this chapter) which is easy to set up, and follows a similar usage pattern to other middleware:

  1. Register services
  2. Add to pipeline

As already mentioned, hosting platforms like IIS, Apache and Nginx provide server-based compression, which should be prioritized ahead of using .NET middleware due to its superior performance characteristics.

.NET middleware compression should be implemented only when the server platform hosting the API does not natively support compression (as is the case with Kestrel), which is most likely going to be for test and development purposes.

What are we implementing

We'll implement .NET middleware compression in this chapter because:

  • This chapter is about compression!
  • It's very easy to add and remove
  • Our API is probably aimed at external consumers
  • We are using Kestrel as our server

Implementing

Program.cs

Open Program.cs and add the following using statement:

using Microsoft.AspNetCore.ResponseCompression;

Then register response compression and set compression levels:

// .
// .
// .
// Existing code

builder.Services.AddResponseCaching();

// Add response compression
builder.Services.AddResponseCompression(options =>
{
options.EnableForHttps = true; // Enable compression for HTTPS
options.Providers.Add<BrotliCompressionProvider>();
options.Providers.Add<GzipCompressionProvider>();

// Add MIME types to compress (these are defaults, but you can customize)
options.MimeTypes = ResponseCompressionDefaults.MimeTypes.Concat(
new[] { "application/json" });
});

// Configure compression levels (optional but recommended)
builder.Services.Configure<BrotliCompressionProviderOptions>(options =>
{
options.Level = System.IO.Compression.CompressionLevel.Fastest;
});

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

builder.Services.AddControllers();

// Existing code
// .
// .
// .

This code:

  • Registers response compression with AddResponseCompression()
  • Enables compression for HTTPS: by default, compression is disabled for HTTPS due to potential security concerns (CRIME & BREACH attacks), we explicitly enable it
  • Adds Brotli and Gzip providers - Brotli offers better compression but has less support; Gzip is universally supported. The client negotiates which to use via the Accept-Encoding header
  • Specifies MIME types to compress - includes default types plus application/json for our API responses
  • Sets compression level to Fastest - prioritizes speed over compression ratio. Options are:
    • Optimal (best ratio, slower)
    • Fastest (quick, decent ratio)
    • NoCompression

Add compression to our middleware pipeline. In terms of placement Microsoft recommend that this should be placed before middleware that requires compression, so we'll place it fairly early in our pipeline.

// .
// .
// .
// Existing code

if (app.Environment.IsDevelopment())
{
app.MapOpenApi();
app.UseHangfireDashboard();
}

app.UseResponseCompression();

app.UseCors("JavaScriptClient");

// Existing code
// .
// .
// .

Exercising

Re-run existing request

Refer back to the response headers you received prior to implementing compression:

HTTP/1.1 200 OK
Connection: close
Content-Type: application/json; charset=utf-8
Date: Wed, 04 Mar 2026 13:42:20 GMT
Server: Kestrel
Cache-Control: public,max-age=300
Transfer-Encoding: chunked

Now make a new request after implementing compression, e.g.:

### Get all platforms
GET {{baseUrl}}/api/platforms

This will now return:

HTTP/1.1 200 OK
Connection: close
Content-Type: application/json; charset=utf-8
Date: Wed, 04 Mar 2026 13:45:28 GMT
Server: Kestrel
Cache-Control: public,max-age=300
Content-Encoding: gzip
Transfer-Encoding: chunked
Vary: Accept-Encoding

{
"items": [
{
"id": 1,
"platformName": "Docker",
"createdAt": "2026-02-19T19:29:33.678947Z"
},
{
"id": 2,
"platformName": "Redis",
"createdAt": "2026-02-19T19:29:42.316227Z"
},
.
.
.

The REST client that we are using has automatically added the following header to the request:

Accept-Encoding: gzip

Hence why we have the following response header:

Content-Encoding: gzip

The content has even been uncompressed for us.

Explicit Accept-Encoding

Update our request as follows:

GET {{baseUrl}}/api/platforms
Accept-Encoding: gzip, br

This explicitly adds an Accept-Encoding header that states that we can accept br (Brotli) compression as well as gzip. Run this request and you'll see:

HTTP/1.1 200 OK
Connection: close
Content-Type: application/json; charset=utf-8
Date: Wed, 04 Mar 2026 13:51:18 GMT
Server: Kestrel
Cache-Control: public,max-age=300
Content-Encoding: br
Transfer-Encoding: chunked
Vary: Accept-Encoding

// A lot of non human-readable characters

This brings back a Brotli encoded payload (br), and unlike gzip payloads, the REST client we are using does not decode the payload automatically for us—hence why you'll see a non human-readable payload.

Why did .NET prioritize Brotli (br) compression over gzip? The ordering of the compression schemes in the client request header makes no difference, .NET simply has Brotli ordered over Gzip by default.

You can change this behavior by passing q values with the Accept-Encoding header as follows:

GET {{baseUrl}}/api/platforms
Accept-Encoding: gzip;q=1.0, br;q=0.8

This should return a gzip response as it has a higher q value:

HTTP/1.1 200 OK
Connection: close
Content-Type: application/json; charset=utf-8
Date: Wed, 04 Mar 2026 14:03:42 GMT
Server: Kestrel
Cache-Control: public,max-age=300
Content-Encoding: gzip
Transfer-Encoding: chunked
Vary: Accept-Encoding

{
"items": [
{
"id": 1,
"platformName": "Docker",
"createdAt": "2026-02-19T19:29:33.678947Z"
},
{
"id": 2,
"platformName": "Redis",
"createdAt": "2026-02-19T19:29:42.316227Z"
}

Version Control

With the code complete, it's time to commit our code. A summary of those steps can be found below, for a more detailed overview refer to Chapter 5

  • Save all files
  • git add .
  • git commit -m "add compression"
  • git push (will fail - copy suggestion)
  • git push --set-upstream origin chapter_25_compression
  • Move to GitHub and complete the PR process through to merging
  • Back at a command prompt: git checkout main
  • git pull

Conclusion

In this chapter, we explored response compression for APIs—what it is, why you'd use it, and the tradeoffs involved. While compression can dramatically reduce payload sizes (60-80% for text-based responses) and improve performance for clients on slower networks, the CPU overhead means it's not universally beneficial. Small responses, internal APIs on fast networks, and high-throughput scenarios may not warrant the cost.

We implemented ASP.NET Core's response compression middleware with both Gzip and Brotli providers, configured compression levels, and positioned the middleware correctly in the request pipeline. We also tested content negotiation using Accept-Encoding headers to see how clients and servers agree on compression formats.

The key takeaway: While .NET middleware compression is easy to implement and works relatively well, it should not be your first choice for production scenarios. Server-based compression solutions from IIS, Apache, or Nginx (amongst others) offer superior performance and are purpose-built for this task. Microsoft explicitly recommends prioritizing these over .NET middleware compression when available.

For production APIs with significant traffic, invest in proper infrastructure-level compression solutions. The .NET middleware approach serves as an excellent learning tool and temporary solution, but mature production systems benefit from dedicated, optimized compression at the web server or reverse proxy layer.

In the next chapter, we'll look at Rate Limiting—another cross-cutting concern where the same production considerations apply.