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

Waleed NaveedWaleed Naveed
5 min read

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:

FeatureK6
ScriptingJavaScript (ES6)
Dev-friendlyCLI-based, CI/CD ready
LightweightSingle binary
ExtensibleEasy to write custom logic
Open-sourceYes

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.

0
Subscribe to my newsletter

Read articles from Waleed Naveed directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Waleed Naveed
Waleed Naveed