K6 is a modern, developer-friendly load testing tool designed for testing the performance and reliability of APIs, microservices, and websites. Built with Go and scriptable in JavaScript, K6 brings performance testing into the DevOps workflow with seamless CI/CD integration and cloud-native capabilities. For comprehensive performance testing strategies, explore our performance testing guide.

Why K6 for Modern Load Testing?

K6 addresses the limitations of traditional performance testing tools by focusing on developer experience and modern infrastructure:

  • JavaScript API: Familiar scripting for frontend and backend developers
  • CLI-first design: Perfect for automation and CI/CD pipelines
  • Performance: Efficient resource usage, can generate high load from single machine
  • Cloud & Local: Run locally during development, scale to cloud for production-level testing
  • Modern protocols: HTTP/1.1, HTTP/2, WebSockets, gRPC support
  • Observability: Real-time metrics, Prometheus integration, Grafana dashboards

K6 vs Traditional Load Testing Tools

For detailed comparisons, see our guides on JMeter, Gatling, and Locust.

FeatureK6JMeterGatlingLocust
Scripting LanguageJavaScriptGUI/XMLScalaPython
Resource EfficiencyExcellentMediumGoodMedium
CI/CD IntegrationNativePluginsGoodGood
Learning CurveLowMediumHighLow
Cloud SupportNative (K6 Cloud)PluginsGatling EnterpriseNo native
Protocol SupportHTTP/2, WS, gRPCExtensiveHTTP/2, WSHTTP
Real-time ResultsExcellentLimitedGoodGood

Getting Started with K6

Installation

# macOS (Homebrew)
brew install k6

# Windows (Chocolatey)
choco install k6

# Linux (Debian/Ubuntu)
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

# Docker
docker pull grafana/k6

Your First K6 Test

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

export const options = {
  vus: 10, // Virtual users
  duration: '30s', // Test duration
};

export default function () {
  const response = http.get('https://test-api.k6.io');

  check(response, {
    'status is 200': (r) => r.status === 200,
    'response time < 500ms': (r) => r.timings.duration < 500,
  });

  sleep(1);
}

Run the test:

k6 run simple-test.js

Test Configuration and Load Profiles

Virtual Users and Duration

export const options = {
  // Simple configuration
  vus: 50,
  duration: '5m',

  // Or use stages for ramping
  stages: [
    { duration: '2m', target: 100 }, // Ramp up to 100 users
    { duration: '5m', target: 100 }, // Stay at 100 users
    { duration: '2m', target: 0 },   // Ramp down to 0 users
  ],
};

Load Testing Scenarios

export const options = {
  scenarios: {
    // Constant load
    constant_load: {
      executor: 'constant-vus',
      vus: 50,
      duration: '5m',
    },

    // Ramping VUs
    ramping_load: {
      executor: 'ramping-vus',
      startVUs: 0,
      stages: [
        { duration: '1m', target: 50 },
        { duration: '3m', target: 50 },
        { duration: '1m', target: 0 },
      ],
    },

    // Constant request rate
    constant_rps: {
      executor: 'constant-arrival-rate',
      rate: 1000, // 1000 requests per second
      timeUnit: '1s',
      duration: '5m',
      preAllocatedVUs: 50,
      maxVUs: 200,
    },

    // Spike test
    spike_test: {
      executor: 'ramping-arrival-rate',
      startRate: 100,
      timeUnit: '1s',
      preAllocatedVUs: 50,
      maxVUs: 500,
      stages: [
        { duration: '30s', target: 100 },  // Normal load
        { duration: '1m', target: 1000 },  // Spike
        { duration: '30s', target: 100 },  // Back to normal
      ],
    },
  },
};

Thresholds for Pass/Fail Criteria

export const options = {
  thresholds: {
    // HTTP errors should be less than 1%
    http_req_failed: ['rate<0.01'],

    // 95% of requests should be below 500ms
    http_req_duration: ['p(95)<500'],

    // 99th percentile should be below 1s
    'http_req_duration{type:api}': ['p(99)<1000'],

    // Custom metric thresholds
    checks: ['rate>0.99'], // 99% of checks should pass
  },
};

Advanced HTTP Testing

HTTP Methods and Request Bodies

import http from 'k6/http';
import { check } from 'k6';

export default function () {
  // GET request
  const getResponse = http.get('https://api.example.com/users');

  // POST with JSON
  const payload = JSON.stringify({
    name: 'John Doe',
    email: 'john@example.com',
  });

  const params = {
    headers: {
      'Content-Type': 'application/json',
      'Authorization': 'Bearer token123',
    },
  };

  const postResponse = http.post(
    'https://api.example.com/users',
    payload,
    params
  );

  check(postResponse, {
    'created successfully': (r) => r.status === 201,
    'has user id': (r) => r.json('id') !== undefined,
  });

  // PUT request
  const updatePayload = JSON.stringify({ name: 'Jane Doe' });
  http.put(`https://api.example.com/users/${postResponse.json('id')}`, updatePayload, params);

  // DELETE request
  http.del(`https://api.example.com/users/${postResponse.json('id')}`, null, params);
}

Batch Requests

import http from 'k6/http';

export default function () {
  // Execute requests in parallel
  const responses = http.batch([
    ['GET', 'https://api.example.com/users'],
    ['GET', 'https://api.example.com/products'],
    ['GET', 'https://api.example.com/orders'],
  ]);

  // Check all responses
  responses.forEach((response, index) => {
    check(response, {
      [`request ${index} status is 200`]: (r) => r.status === 200,
    });
  });
}

Authentication and Session Management

import http from 'k6/http';
import { check } from 'k6';

export function setup() {
  // Login once per test run
  const loginResponse = http.post('https://api.example.com/auth/login', {
    username: 'testuser',
    password: 'testpass',
  });

  return { token: loginResponse.json('token') };
}

export default function (data) {
  // Use token from setup
  const params = {
    headers: {
      'Authorization': `Bearer ${data.token}`,
    },
  };

  const response = http.get('https://api.example.com/protected', params);

  check(response, {
    'authenticated': (r) => r.status === 200,
  });
}

Data Parameterization

Using CSV Data

import { SharedArray } from 'k6/data';
import papaparse from 'https://jslib.k6.io/papaparse/5.1.1/index.js';

const csvData = new SharedArray('users', function () {
  return papaparse.parse(open('./users.csv'), { header: true }).data;
});

export default function () {
  // Get random user
  const user = csvData[Math.floor(Math.random() * csvData.length)];

  const response = http.post('https://api.example.com/login', {
    username: user.username,
    password: user.password,
  });

  check(response, {
    'login successful': (r) => r.status === 200,
  });
}

Using JSON Data

import { SharedArray } from 'k6/data';

const testData = new SharedArray('products', function () {
  return JSON.parse(open('./products.json'));
});

export default function () {
  const product = testData[__VU % testData.length]; // Round-robin selection

  http.post('https://api.example.com/cart/add', JSON.stringify({
    productId: product.id,
    quantity: 1,
  }), {
    headers: { 'Content-Type': 'application/json' },
  });
}

Custom Metrics and Tags

Custom Metrics

import http from 'k6/http';
import { Trend, Counter, Rate, Gauge } from 'k6/metrics';

const myTrend = new Trend('waiting_time');
const myCounter = new Counter('my_counter');
const myRate = new Rate('my_rate');
const myGauge = new Gauge('my_gauge');

export default function () {
  const response = http.get('https://api.example.com');

  // Add custom metrics
  myTrend.add(response.timings.waiting);
  myCounter.add(1);
  myRate.add(response.status === 200);
  myGauge.add(response.timings.duration);
}

Tags for Organizing Metrics

import http from 'k6/http';
import { check } from 'k6';

export default function () {
  // Tag individual requests
  const response = http.get('https://api.example.com/users', {
    tags: {
      type: 'api',
      endpoint: 'users',
    },
  });

  check(response, {
    'status 200': (r) => r.status === 200,
  }, { type: 'api' }); // Tag checks
}

export const options = {
  thresholds: {
    // Threshold for specific tags
    'http_req_duration{type:api}': ['p(95)<500'],
    'http_req_duration{endpoint:users}': ['p(99)<1000'],
  },
};

Real-World Testing Scenarios

E-commerce User Flow

This scenario demonstrates comprehensive load testing similar to patterns in API performance testing:

import http from 'k6/http';
import { check, group, sleep } from 'k6';

export const options = {
  stages: [
    { duration: '2m', target: 50 },
    { duration: '5m', target: 50 },
    { duration: '2m', target: 0 },
  ],
  thresholds: {
    http_req_duration: ['p(95)<500'],
    'http_req_duration{page:home}': ['p(95)<300'],
    'http_req_duration{page:product}': ['p(95)<400'],
  },
};

export default function () {
  let response;

  group('Homepage', function () {
    response = http.get('https://ecommerce.example.com', {
      tags: { page: 'home' },
    });
    check(response, { 'homepage loaded': (r) => r.status === 200 });
    sleep(1);
  });

  group('Search Products', function () {
    response = http.get('https://ecommerce.example.com/search?q=laptop', {
      tags: { page: 'search' },
    });
    check(response, { 'search results': (r) => r.status === 200 });
    sleep(2);
  });

  group('View Product', function () {
    response = http.get('https://ecommerce.example.com/products/123', {
      tags: { page: 'product' },
    });
    check(response, { 'product details': (r) => r.status === 200 });
    sleep(3);
  });

  group('Add to Cart', function () {
    response = http.post(
      'https://ecommerce.example.com/cart',
      JSON.stringify({ productId: 123, quantity: 1 }),
      {
        headers: { 'Content-Type': 'application/json' },
        tags: { page: 'cart' },
      }
    );
    check(response, { 'added to cart': (r) => r.status === 200 });
    sleep(1);
  });

  group('Checkout', function () {
    response = http.post(
      'https://ecommerce.example.com/checkout',
      JSON.stringify({
        payment: 'card',
        address: '123 Main St',
      }),
      {
        headers: { 'Content-Type': 'application/json' },
        tags: { page: 'checkout' },
      }
    );
    check(response, { 'checkout successful': (r) => r.status === 200 });
  });
}

API Load Test with Error Handling

import http from 'k6/http';
import { check, sleep } from 'k6';
import { Rate } from 'k6/metrics';

const errorRate = new Rate('errors');

export const options = {
  scenarios: {
    stress_test: {
      executor: 'ramping-arrival-rate',
      startRate: 10,
      timeUnit: '1s',
      preAllocatedVUs: 50,
      maxVUs: 500,
      stages: [
        { duration: '1m', target: 10 },
        { duration: '2m', target: 50 },
        { duration: '2m', target: 100 },
        { duration: '1m', target: 10 },
      ],
    },
  },
  thresholds: {
    errors: ['rate<0.1'], // Error rate should be less than 10%
    http_req_duration: ['p(95)<2000', 'p(99)<5000'],
  },
};

export default function () {
  const response = http.get('https://api.example.com/data');

  const success = check(response, {
    'status is 200': (r) => r.status === 200,
    'response time OK': (r) => r.timings.duration < 1000,
    'no errors in body': (r) => !r.body.includes('error'),
  });

  errorRate.add(!success);

  if (response.status !== 200) {
    console.error(`Request failed: ${response.status} ${response.body}`);
  }

  sleep(1);
}

CI/CD Integration

GitHub Actions

name: Load Test

on:
  push:
    branches: [main]
  schedule:
    - cron: '0 2 * * *' # Daily at 2 AM

jobs:
  k6_load_test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3

      - name: Run K6 Load Test
        uses: grafana/k6-action@v0.3.1
        with:
          filename: tests/load-test.js
          cloud: true
          token: ${{ secrets.K6_CLOUD_TOKEN }}

      - name: Upload Results
        uses: actions/upload-artifact@v3
        if: always()
        with:
          name: k6-results
          path: results/

Jenkins Pipeline

pipeline {
    agent any

    stages {
        stage('Load Test') {
            steps {
                sh '''
                    k6 run --out json=results.json \
                           --out influxdb=http://influxdb:8086/k6 \
                           tests/load-test.js
                '''
            }
        }

        stage('Check Thresholds') {
            steps {
                script {
                    def exitCode = sh(
                        script: 'k6 run --quiet tests/load-test.js',
                        returnStatus: true
                    )
                    if (exitCode != 0) {
                        error("Load test thresholds failed")
                    }
                }
            }
        }
    }

    post {
        always {
            archiveArtifacts artifacts: 'results.json', fingerprint: true
        }
    }
}

GitLab CI

k6-test:
  stage: test
  image: grafana/k6:latest
  script:
    - k6 run --summary-export=summary.json tests/load-test.js
  artifacts:
    when: always
    paths:
      - summary.json
    reports:
      junit: summary.json

Observability and Results Analysis

Output Options

# JSON output
k6 run --out json=results.json script.js

# CSV output
k6 run --out csv=results.csv script.js

# InfluxDB + Grafana
k6 run --out influxdb=http://localhost:8086/k6 script.js

# Prometheus Remote Write
k6 run --out experimental-prometheus-rw script.js

# K6 Cloud
k6 run --out cloud script.js

# Multiple outputs
k6 run --out json=results.json --out influxdb=http://localhost:8086/k6 script.js

Reading Results

# Summary statistics
k6 run script.js

# Detailed output
k6 run --verbose script.js

# Quiet mode (only errors)
k6 run --quiet script.js

# Save summary to file
k6 run --summary-export=summary.json script.js

Custom Summary

import { htmlReport } from "https://raw.githubusercontent.com/benc-uk/k6-reporter/main/dist/bundle.js";
import { textSummary } from "https://jslib.k6.io/k6-summary/0.0.1/index.js";

export function handleSummary(data) {
  return {
    "summary.html": htmlReport(data),
    "summary.json": JSON.stringify(data),
    stdout: textSummary(data, { indent: " ", enableColors: true }),
  };
}

Best Practices

Resource Efficiency

// ❌ Bad: Creates new object every iteration
export default function () {
  const headers = { 'Content-Type': 'application/json' };
  http.get('https://api.example.com', { headers });
}

// ✓ Good: Reuse objects
const params = {
  headers: { 'Content-Type': 'application/json' },
};

export default function () {
  http.get('https://api.example.com', params);
}

Think Time and Pacing

import { sleep } from 'k6';

export default function () {
  // Simulate user think time
  http.get('https://api.example.com/page1');
  sleep(Math.random() * 3 + 2); // 2-5 seconds

  http.get('https://api.example.com/page2');
  sleep(Math.random() * 2 + 1); // 1-3 seconds
}

Modular Test Scripts

// modules/auth.js
export function login(username, password) {
  const response = http.post('https://api.example.com/login', {
    username,
    password,
  });
  return response.json('token');
}

// modules/users.js
export function getUser(token, userId) {
  return http.get(`https://api.example.com/users/${userId}`, {
    headers: { Authorization: `Bearer ${token}` },
  });
}

// main-test.js
import { login } from './modules/auth.js';
import { getUser } from './modules/users.js';

export default function () {
  const token = login('user', 'pass');
  getUser(token, 123);
}

Conclusion

K6 represents a paradigm shift in performance testing, bringing developer-friendly tooling and modern practices to load testing. Its JavaScript API, efficient resource usage, and seamless integration with CI/CD pipelines make it ideal for DevOps teams practicing continuous testing. Combine K6 with monitoring and observability tools for comprehensive performance validation.

Key advantages:

  • Developer-friendly JavaScript API
  • Efficient resource usage for high load generation
  • Native CI/CD integration
  • Modern protocol support (HTTP/2, WebSockets, gRPC)
  • Flexible execution modes (local, cloud, hybrid)
  • Rich ecosystem and observability options

Whether you’re testing microservices, APIs, or full web applications, K6 provides the tools and flexibility to ensure your systems perform reliably under load. By integrating performance testing early in the development cycle, teams can catch issues before they reach production and maintain high-quality, performant applications. For complementary testing approaches, explore database performance testing and WebSocket performance testing.