Your First K6 Script

8 min read

Writing a K6 script for the first time is deliberately simple. The minimum working test is five lines. This lesson builds that script from scratch, explains every line, and shows you how the pieces fit together.

The minimal working script

Create a file called script.js:

import http from 'k6/http';
import { check, sleep } from 'k6';
 
export default function () {
  const res = http.get('https://test.k6.io');
  check(res, {
    'status is 200': (r) => r.status === 200,
  });
  sleep(1);
}

Run it:

k6 run script.js

K6 sends one GET request, records whether the check passed, pauses one second, prints a summary, and exits. You just ran a load test.

What each line does

import http from 'k6/http'

Imports K6's built-in HTTP client. This is the module you use for every HTTP request — GET, POST, PUT, DELETE, PATCH. Unlike a browser's fetch or Node's axios, k6/http is purpose-built for load testing: it automatically tracks DNS lookup time, TCP handshake time, TLS setup, time-to-first-byte, and download time as separate metrics on every single request.

import { check, sleep } from 'k6'

Imports two utilities from K6's core module. check() records whether a named condition is true or false without stopping the test. sleep() pauses the current virtual user for a given number of seconds — essential for realistic simulation, because real users do not hammer endpoints without pausing between actions.

export default function ()

This is the virtual user function. K6 calls it once per iteration, for every virtual user running. With 10 VUs running for 30 seconds, this function body executes several hundred times. The export default syntax is required — it is how K6 identifies the test entry point.

http.get('https://test.k6.io')

Sends an HTTP GET request. Returns a response object res with:

  • res.status — the HTTP status code (200, 404, 500, etc.)
  • res.body — the response body as a string
  • res.json() — the body parsed as JSON (or a specific field via res.json('fieldName'))
  • res.timings — object with duration, blocked, connecting, waiting, receiving, and more

check(res, { 'status is 200': (r) => r.status === 200 })

Records named assertions. The second argument is an object where each key is a human-readable label and each value is a function returning true or false. Failed checks do NOT stop the test or cause a non-zero exit code on their own — they accumulate in the checks metric. Chapter 3 covers how to make check failures actually fail the test using thresholds.

sleep(1)

Pauses the current VU for one second. Without this, VUs send requests as fast as the server responds — which is not realistic and inflates your throughput numbers to values no real workload would produce. Think of sleep as modelling "user reading the page."

The structure of a K6 script

K6 script
  • – k6/http — HTTP requests
  • – k6 — check, sleep, group
  • – k6/metrics — custom metrics
  • – k6/data — SharedArray, CSV
  • – vus — virtual user count
  • – duration — how long to run
  • – stages — ramp up / down
  • – thresholds — pass/fail gates
  • – Runs once before VUs start
  • – Auth, test data, warmup
  • – Returns data for all VUs
  • Runs once per iteration per VU –
  • HTTP requests, checks, sleeps –
  • The actual test logic –
  • Runs once after all VUs finish –
  • Cleanup, final logging –

Not every K6 script needs all five sections. The minimum is just an export default function. options, setup(), and teardown() are optional — they are covered in Chapter 2.

A slightly more complete script

Here is the same test with options added so you control VUs and duration from the script instead of CLI flags:

import http from 'k6/http';
import { check, sleep } from 'k6';
 
export const options = {
  vus: 5,
  duration: '30s',
};
 
export default function () {
  const res = http.get('https://test.k6.io');
 
  check(res, {
    'status is 200': (r) => r.status === 200,
    'response time under 500ms': (r) => r.timings.duration < 500,
  });
 
  sleep(1);
}

options.vus: 5 runs five virtual users concurrently. options.duration: '30s' runs the test for thirty seconds — K6 stops automatically when the duration expires.

K6's built-in modules at a glance

ModulePurpose
k6/httpHTTP requests — GET, POST, PUT, DELETE, PATCH, batch
k6check, sleep, group, fail
k6/metricsCustom metric types: Trend, Counter, Gauge, Rate
k6/dataSharedArray for memory-efficient test data loading
k6/encodingBase64 encoding/decoding for auth headers
k6/cryptoHashing and HMAC for signing requests

Sending a POST request

Most API testing involves sending data. Here is a POST with a JSON body — the pattern you will use constantly:

import http from 'k6/http';
import { check } from 'k6';
 
export default function () {
  const payload = JSON.stringify({
    username: 'testuser',
    email: 'test@example.com',
  });
 
  const params = {
    headers: { 'Content-Type': 'application/json' },
  };
 
  const res = http.post('https://httpbin.org/post', payload, params);
 
  check(res, {
    'status is 200': (r) => r.status === 200,
    'response has json field': (r) => r.json('json') !== null,
  });
}

Three arguments to http.post(): the URL, the body (must be a string — use JSON.stringify()), and a params object for headers and other options. K6 does not automatically serialise objects.

⚠️ Common mistakes

  • Forgetting sleep(). Without a pause between requests, each VU sends requests as fast as the server responds. Throughput numbers look impressive but bear no resemblance to real user behaviour — and results vary dramatically based on server response time rather than the load you are applying.
  • Using check() without a threshold and expecting the test to fail. check() records pass/fail but never produces a non-zero exit code on its own. Add thresholds: { 'checks': ['rate>0.99'] } to your options to make failed checks fail the build.
  • Importing from Node.js modules. import fs from 'fs' throws at runtime. For reading files in K6, use the built-in open() function in init code, or SharedArray for large datasets (Chapter 5).

🎯 Practice task

Write and run your own K6 script from scratch. 20 minutes.

  1. Create myfirst.js with the minimal five-line script above. Run it with k6 run myfirst.js — confirm it runs with 1 VU, 1 iteration.
  2. Add export const options = { vus: 3, duration: '15s' }; and run again. Watch the iteration count increase.
  3. Add a second check: 'response body not empty': (r) => r.body.length > 0.
  4. Change the URL to https://httpbin.org/get. Run again and inspect what res.json('url') returns — add a check that verifies it equals your URL.
  5. Modify to a POST against https://httpbin.org/post sending a JSON body. Add a check that the response echoes your payload back.

By the end of this task you will have written five variations of a K6 script and understand exactly what every part does.

// tip to track lessons you complete and pick up where you left off across devices.