Load Testing .NET APIs with K6 – A Developer’s Guide


In this blog, we’ll explore how to load test a real .NET Web API using K6, a modern open-source tool for performance testing built specifically for developers.
You'll learn:
What is load testing, and why it matters
Why K6 is the go-to tool for developers
How to build a basic .NET API with public and secure endpoints
How to write and run K6 scripts
How to interpret test results to catch performance issues early
What is Load Testing?
Load testing is a type of performance testing that simulates real-world traffic to evaluate how your application performs under pressure.
It helps answer questions like:
Can my API handle 500 users hitting it at once?
Does response time degrade under load?
Are there memory spikes or server crashes?
The earlier you find bottlenecks, the cheaper they are to fix.
Why K6?
Most traditional load testing tools (like JMeter or LoadRunner) are heavy and often used by QA teams. K6, on the other hand, is built for developers:
Feature | K6 |
Scripting | JavaScript (ES6) |
Dev-friendly | CLI-based, CI/CD ready |
Lightweight | Single binary |
Extensible | Easy to write custom logic |
Open-source | Yes |
K6 allows developers to write load tests as code, making it easy to version control, reuse, and automate.
Our .NET API (Quick Overview)
We’ve created a simple .NET 8 Web API
project with two types of endpoints:
Public endpoint:
GET /api/Product/public
(anyone can call this)Secure endpoint:
GET /api/Product/secure?id={id}
(requires JWT authentication)Login endpoint:
POST /api/Auth/login
(returns JWT token for a valid user)
We’re using an in-memory database to keep things simple — seeded with one user and a few sample products. No external database or hosting required.
You can clone this project and run it locally in minutes.
Install K6
To get started with K6, install it from K6 website
Verify installation:
k6 version
Load Testing Scenarios
We'll simulate 3 different test cases:
1. Public Endpoint Test (No Auth)
import http from 'k6/http';
import { check, sleep } from 'k6';
export const options = {
vus: 100,
duration: '60s',
};
export default function () {
const res = http.get('https://localhost:7061/api/Product/public');
check(res, {
'status is 200': (r) => r.status === 200,
});
sleep(1);
}
Copy the above code in a file publicEndpointTest.js
. Run the following command to execute the test:
k6 run publicEndpointTest.js
Output:
2. Secure Endpoint with Token Fetch
We'll first log in, using a dummy user seeded into our in-memory database, via /login
endpoint and use the token to access the secure endpoint.
import http from 'k6/http';
import { check, sleep } from 'k6';
export const options = {
vus: 100,
duration: '60s',
};
// Setup: Run once, fetch token
export function setup() {
const loginPayload = JSON.stringify({
username: 'admin@example.com',
password: '123456',
});
const loginHeaders = {
headers: { 'Content-Type': 'application/json' },
};
const res = http.post('https://localhost:7061/api/Auth/login', loginPayload, loginHeaders);
const token = res.json().result.token;
return token;
}
// Test: Run per VU, using token
export default function (token) {
const res = http.get('https://localhost:7061/api/Product/secure?id=1', {
headers: {
Authorization: `Bearer ${token}`,
},
});
check(res, {
'is status 200': (r) => r.status === 200,
});
sleep(1);
}
Copy the above code in a file secureEndpointWithTokenFetch.js
. Run the following command to execute the test:
k6 run secureEndpointWithTokenFetch.js
Output:
3. Secure Endpoint with Hardcoded Token
In real-world load tests, you often use a pre-generated JWT so login isn't part of the performance test.
import http from 'k6/http';
import { check, sleep } from 'k6';
// Replace this with an actual token copied from login response
const token = '';
export const options = {
vus: 100,
duration: '60s',
};
export default function () {
const secureParams = {
headers: {
Authorization: `Bearer ${token}`,
},
};
const res = http.get('https://localhost:7061/api/Product/secure?id=1', secureParams);
check(res, {
'status is 200': (r) => r.status === 200,
});
sleep(1);
}
Copy the above code in a file secureEndpointWithHardcodedToken.js
. Run the following command to execute the test:
k6 run secureEndpointWithHardcodedToken.js
Output:
Understanding the Output
K6 provides a useful summary after each test:
vus (virtual users): Simulated users
iterations: Total API calls made
http_req_duration: Response time per request
http_req_failed: Number of failed requests
Use this data to:
Set performance benchmarks
Identify spikes or errors under load
Tune your API before production
A Note on JWT Auth Testing
You should never include login as part of your performance test (unless you're testing the login endpoint itself). Fetch a token beforehand or use a mock token.
Tips
If you're using HTTPS locally, K6 might complain. Run with
--insecure-skip-tls-verify
flag:k6 run --insecure-skip-tls-verify publicEndpointTest.js
You can also simulate increased load:
k6 run --vus 50 --duration 30s publicEndpointTest.js
Conclusion
We built a simple .NET API with both public and secure endpoints, then used K6 to load test it under various scenarios. You now have:
A practical understanding of how K6 works
Scripts to test real-world endpoints
A local project you can expand or demo
K6 is ideal for developers who want early feedback on performance — before the app hits production.
GitHub Repo
Code and scripts are available on GitHub.
Subscribe to my newsletter
Read articles from Waleed Naveed directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
