DEV Community

1xApi
1xApi

Posted on • Originally published at 1xapi.com

How to Implement API Load Testing with k6 in Node.js (2026 Guide)

How to Implement API Load Testing with k6 in Node.js (2026 Guide)

Load testing is one of those things every developer knows they should do but often skips because the tooling feels overwhelming. You've built an amazing API in Node.js, deployed it to production, and then—disaster. Your API crashes under real traffic because you never tested how it performs under load.

In this guide, I'll show you how to use k6, the modern load testing tool that's become the go-to choice for developers in 2026. We'll cover writing realistic test scripts, defining performance thresholds, and integrating load testing into your CI/CD pipeline.

What is k6 and Why Should You Care?

k6 is an open-source load testing tool developed by Grafana (yes, the same company behind Grafana observability). Unlike traditional tools that require steep learning curves, k6 lets you write tests in plain JavaScript. If you know how to write a Node.js API, you already know how to write k6 tests.

Here's why developers love k6 in 2026:

  • Developer-friendly API: Write tests in JavaScript, not some proprietary scripting language
  • Built-in checks and thresholds: Define pass/fail criteria that actually matter
  • CI/CD integration: Run load tests as part of your deployment pipeline
  • Grafana integration: Visualize results in dashboards you already use
  • Cloud and local execution: Test locally or scale to hundreds of thousands of virtual users in the cloud

Setting Up k6 for API Testing

First, install k6 on your machine. On macOS, it's a simple Homebrew install:

brew install k6
Enter fullscreen mode Exit fullscreen mode

On Linux, use the official script:

sudo gpg -k
sudo gpg --no-default-keyring --keyring /usr/share/keyrings/k6-archive-keyring.gpg --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys C5AD17C747E3415A3642D57D77C6C491D6AC1D69
echo "deb [signed-by=/usr/share/keyrings/k6-archive-keyring.gpg] https://dl.k6.io/deb stable main" | sudo tee /etc/apt/sources.list.d/k6.list
sudo apt-get update
sudo apt-get install k6
Enter fullscreen mode Exit fullscreen mode

For Windows, download the installer from the k6 website.

Writing Your First API Load Test

Let's create a test script for a typical REST API. We'll test an endpoint that returns a list of resources.

Create a file called api-test.js:

// api-test.js
import http from 'k6/http';
import { check, sleep } from 'k6';

// Test configuration
export const options = {
  // Virtual users - how many concurrent users
  vus: 20,
  // Test duration
  duration: '1m',
  // Performance thresholds - these define pass/fail criteria
  thresholds: {
    // 95th percentile response time must be under 500ms
    http_req_duration: ['p(95)<500'],
    // 99th percentile must be under 1 second
    'http_req_duration{expected_response:true}': ['p(99)<1000'],
    // Error rate must be under 1%
    http_req_failed: ['rate<0.01'],
    // 95% of checks must pass
    checks: ['rate>0.95'],
    // Minimum throughput of 10 requests per second
    http_reqs: ['rate>10'],
  },
};

// This function runs for each virtual user
default function () {
  // Make a GET request to your API
  const baseUrl = 'https://your-api.example.com';
  const response = http.get(`${baseUrl}/api/v1/resources`);

  // Check that the response is successful
  check(response, {
    'status is 200': (r) => r.status === 200,
    'response time OK': (r) => r.timings.duration < 400,
    'has correct content-type': (r) => r.headers['Content-Type'].includes('application/json'),
  });

  // Simulate realistic user behavior with sleep
  sleep(0.5);
}
Enter fullscreen mode Exit fullscreen mode

Testing Multiple API Endpoints with Scenarios

Real APIs have multiple endpoints with different traffic patterns. In 2026, k6's scenario feature lets you model complex traffic patterns accurately.

// multi-endpoint-test.js
import http from 'k6/http';
import { check, sleep } from 'k6';
import { Rate, Trend } from 'k6/metrics';

// Custom metrics
const errorRate = new Rate('errors');
const apiDuration = new Trend('api_duration');

export const options = {
  scenarios: {
    // Smoke test - light load to verify basic functionality
    smoke: {
      executor: 'constant-vus',
      vus: 5,
      duration: '30s',
      tags: { test_type: 'smoke' },
    },
    // Load test - normal expected traffic
    load: {
      executor: 'ramping-vus',
      startVUs: 0,
      stages: [
        { duration: '2m', target: 50 },  // Ramp up to 50 users
        { duration: '5m', target: 50 },  // Hold at 50 users
        { duration: '2m', target: 0 },   // Ramp down
      ],
      tags: { test_type: 'load' },
    },
    // Stress test - push beyond normal capacity
    stress: {
      executor: 'ramping-vus',
      startVUs: 0,
      stages: [
        { duration: '1m', target: 100 },
        { duration: '2m', target: 200 },
        { duration: '2m', target: 300 },
        { duration: '1m', target: 0 },
      ],
      tags: { test_type: 'stress' },
    },
  },
  thresholds: {
    http_req_duration: ['p(95)<500', 'p(99)<1000'],
    http_req_failed: ['rate<0.01'],
    errors: ['rate<0.1'],
  },
};

const baseUrl = 'https://your-api.example.com';

// Test different endpoints
export default function () {
  // Test GET /api/v1/resources
  const getResponse = http.get(`${baseUrl}/api/v1/resources`);
  check(getResponse, {
    'GET resources - status 200': (r) => r.status === 200,
    'GET resources - has data': (r) => r.json('data') !== undefined,
  });
  errorRate.add(getResponse.status !== 200);
  apiDuration.add(getResponse.timings.duration);
  sleep(0.3);

  // Test POST /api/v1/resources (create new resource)
  const createPayload = JSON.stringify({
    name: 'Test Resource',
    type: 'example',
  });
  const postResponse = http.post(`${baseUrl}/api/v1/resources`, createPayload, {
    headers: { 'Content-Type': 'application/json' },
  });
  check(postResponse, {
    'POST resource - status 201': (r) => r.status === 201,
    'POST resource - has id': (r) => r.json('id') !== undefined,
  });
  errorRate.add(postResponse.status !== 201);
  apiDuration.add(postResponse.timings.duration);
  sleep(0.3);

  // Test GET /api/v1/users (authentication endpoint)
  const authResponse = http.get(`${baseUrl}/api/v1/users/me`, {
    headers: { 'Authorization': `Bearer ${__ENV.API_TOKEN}` },
  });
  check(authResponse, {
    'GET user - status 200': (r) => r.status === 200,
  });
  errorRate.add(authResponse.status !== 200);
}
Enter fullscreen mode Exit fullscreen mode

Testing API Authentication

Most production APIs require authentication. Here's how to test authenticated endpoints:

// authenticated-test.js
import http from 'k6/http';
import { check, sleep } from 'k6';
import { RefinedSession } from 'k6/http';

export const options = {
  vus: 10,
  duration: '1m',
};

const baseUrl = 'https://your-api.example.com';

// Get access token once per virtual user
export function setup() {
  const loginResponse = http.post(`${baseUrl}/api/v1/auth/login`, 
    JSON.stringify({
      email: 'test@example.com',
      password: 'testpassword123',
    }),
    {
      headers: { 'Content-Type': 'application/json' },
    }
  );

  const token = loginResponse.json('access_token');
  return { token };
}

// Use the token in requests
default function (data) {
  const headers = {
    'Authorization': `Bearer ${data.token}`,
    'Content-Type': 'application/json',
  };

  // Test authenticated endpoint
  const response = http.get(`${baseUrl}/api/v1/profile`, { headers });

  check(response, {
    'authenticated request succeeds': (r) => r.status === 200,
    'response has user data': (r) => r.json('email') !== undefined,
  });

  sleep(0.5);
}
Enter fullscreen mode Exit fullscreen mode

Running k6 Tests

Execute your tests with the k6 CLI:

# Run locally with output to terminal
k6 run api-test.js

# Run with cloud results (requires k6 account)
k6 run --out cloud api-test.js

# Run with environment variables
k6 run -e API_TOKEN=your_token_here authenticated-test.js

# Run specific scenario
k6 run --env TEST_TYPE=load multi-endpoint-test.js
Enter fullscreen mode Exit fullscreen mode

Integrating k6 into CI/CD

The real power of load testing comes from running it automatically. Here's how to integrate k6 into your GitHub Actions workflow:

# .github/workflows/load-test.yml
name: API Load Tests

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

jobs:
  load-test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Setup k6
        uses: grafana/k6-action@v0.2.0
        with:
          version: 'latest'

      - name: Run smoke test
        run: k6 run smoke-test.js
        env:
          API_URL: ${{ secrets.API_URL }}
          API_TOKEN: ${{ secrets.API_TOKEN }}

      - name: Run load test (on main branch only)
        if: github.ref == 'refs/heads/main'
        run: k6 run load-test.js
        env:
          API_URL: ${{ secrets.API_URL }}
          API_TOKEN: ${{ secrets.API_TOKEN }}
Enter fullscreen mode Exit fullscreen mode

Analyzing k6 Results

When k6 completes, you'll see a summary like this:

     ✓ status is 200
     ✓ response time OK
     ✓ has correct content-type

     checks.........................: 95.00% ✓ 2850   ✗ 150
     http_req_blocked...............: avg=5.23ms  min=0s    med=2ms    max=98ms   p(95)=15ms
     http_req_connecting............: avg=3.12ms  min=0s    med=1ms    max=45ms   p(95)=8ms
     http_req_duration.............: avg=125ms   min=80ms  med=110ms  max=450ms  p(95)=250ms
     http_req_failed................: 0.50%   ✓ 15     ✗ 1485
     http_reqs......................: 25/sec  min=0    med=25    max=35     p(95)=32
Enter fullscreen mode Exit fullscreen mode

Key metrics to watch:

  • http_req_duration: How long requests take (p95 and p99 matter most)
  • http_req_failed: Percentage of failed requests
  • http_reqs: Throughput (requests per second)
  • checks: Percentage of custom assertions that passed

Best Practices for API Load Testing in 2026

  1. Test in production-like environments: Your staging environment should mirror production closely
  2. Use realistic data: Test with data volumes similar to production
  3. Test incrementally: Start with smoke tests, then load tests, then stress tests
  4. Set meaningful thresholds: Don't just guess—baseline your API's normal performance
  5. Monitor during tests: Watch your API's metrics (CPU, memory, database connections) while testing
  6. Test regularly: Run load tests after significant code changes
  7. Use k6 Cloud for scale: For realistic high-load testing, k6 Cloud can generate millions of requests

Conclusion

Load testing doesn't have to be complicated. With k6, you can write tests in JavaScript that are easy to understand, maintain, and integrate into your development workflow. The key is starting simple—test your critical endpoints first, establish baselines, and gradually add more complex scenarios.

Remember: it's better to catch performance issues in testing than in production. Your users will thank you.


Ready to level up your API testing? 1xAPI provides reliable, scalable APIs for developers. Check out our API marketplace for production-ready APIs you can integrate into your applications today.

Top comments (0)