GET, POST, PUT, DELETE in K6

9 min read

K6's HTTP module covers every method you will need for REST API load testing. This lesson walks through each one — GET, POST, PUT, PATCH, DELETE — plus batch requests and request tagging. All of them return the same response object, and learning to read it efficiently saves time across every test you write.

The HTTP methods

GET — reading resources

import http from 'k6/http';
 
export default function () {
  // Simple GET
  const res = http.get('https://api.test.com/users');
 
  // GET with query parameters built into the URL
  const filtered = http.get('https://api.test.com/users?role=admin&active=true');
 
  // GET with headers
  const authRes = http.get('https://api.test.com/protected', {
    headers: { Authorization: 'Bearer mytoken' },
  });
}

POST — creating resources

const payload = JSON.stringify({
  name: 'Alice',
  email: 'alice@test.com',
  role: 'admin',
});
 
const params = {
  headers: { 'Content-Type': 'application/json' },
};
 
const res = http.post('https://api.test.com/users', payload, params);

Three things to remember every time you POST JSON: serialise with JSON.stringify(), set Content-Type: application/json, and pass both as the second and third arguments. K6 will not serialise plain objects for you.

PUT and PATCH — updating resources

const updated = JSON.stringify({ name: 'Alicia', role: 'superadmin' });
const params = { headers: { 'Content-Type': 'application/json' } };
 
// PUT replaces the entire resource
http.put('https://api.test.com/users/1', updated, params);
 
// PATCH applies a partial update
http.patch('https://api.test.com/users/1', updated, params);

The function signatures are identical to POST: URL, body, params.

DELETE — removing resources

// DELETE usually has no body
http.del('https://api.test.com/users/1');
 
// DELETE with auth
http.del('https://api.test.com/users/1', null, {
  headers: { Authorization: `Bearer ${token}` },
});

Note the method name is http.del, not http.deletedelete is a reserved keyword in JavaScript.

The request lifecycle — what K6 measures

http_req_duration is the total time from sending the request to receiving the last byte. When it is high, compare the breakdown: if http_req_waiting dominates, the server is slow to process; if http_req_receiving dominates, the response payload is large; if http_req_blocked dominates, the connection pool is saturated.

Reading the response object

Every HTTP method returns the same response object:

const res = http.post('https://api.test.com/users', payload, params);
 
console.log(res.status);                     // 201
console.log(res.body);                       // '{"id":42,"name":"Alice"}'
console.log(res.json());                     // { id: 42, name: 'Alice' }
console.log(res.json('name'));               // 'Alice' — JSONPath-like extraction
console.log(res.json('data.user.email'));    // nested field access
console.log(res.headers['Content-Type']);    // 'application/json'
console.log(res.timings.duration);          // total time in ms
console.log(res.timings.waiting);           // TTFB in ms

res.json('fieldName') extracts a field from a JSON body without a separate parse call. It supports dot notation for nested fields.

Batch requests — parallel within a VU

A single VU can send multiple requests in parallel using http.batch(). This models page loads that trigger several simultaneous API calls:

const responses = http.batch([
  ['GET', 'https://api.test.com/users'],
  ['GET', 'https://api.test.com/products'],
  ['GET', 'https://api.test.com/orders'],
]);
 
const [usersRes, productsRes, ordersRes] = responses;

All three requests are sent in parallel. Without http.batch() they would be sequential — tripling the time a VU spends waiting per iteration. Use batch when your system would naturally serve these requests simultaneously (a dashboard page loading multiple widgets, for example).

Tagging requests for metric filtering

By default, all requests contribute to a single http_req_duration metric. Tags split this by endpoint so you can apply per-endpoint thresholds:

http.get('https://api.test.com/users', { tags: { name: 'GetUsers' } });
http.post('https://api.test.com/orders', payload, {
  headers: { 'Content-Type': 'application/json' },
  tags: { name: 'CreateOrder' },
});

The output and Grafana dashboards then show separate metric rows:

http_req_duration{name:GetUsers}.....: p(95)=120ms
http_req_duration{name:CreateOrder}..: p(95)=340ms

You can apply separate thresholds to each:

thresholds: {
  'http_req_duration{name:GetUsers}':    ['p(95)<200'],
  'http_req_duration{name:CreateOrder}': ['p(95)<800'],
},

⚠️ Common mistakes

  • Passing a plain object as the POST body. http.post(url, { name: 'Alice' }) sends [object Object] as the body string. Always call JSON.stringify() first and set Content-Type: application/json.
  • Using http.delete() instead of http.del(). delete is a reserved JavaScript keyword. K6 exposes the method as http.del(). Writing http.delete() throws a syntax error.
  • Forgetting tags in multi-endpoint tests. Without tags, all requests contribute to one aggregated average — making it impossible to see which endpoint is causing a slow p(95). Tag every request with a meaningful name from the start.

🎯 Practice task

Build a CRUD test against a public REST API. 30 minutes.

Use https://jsonplaceholder.typicode.com — free, no auth required.

  1. Write a K6 script with vus: 3, duration: '30s' that performs a CRUD cycle per iteration:
    • GET /posts/1 — check status 200 and res.json('title') is a non-empty string.
    • POST /posts with JSON.stringify({ title: 'Test', body: 'K6 test', userId: 1 }) — check status 201.
    • PUT /posts/1 with an updated payload — check status 200.
    • DELETE /posts/1 — check status 200.
  2. Tag each request with { tags: { name: 'OperationName' } }.
  3. Run and confirm you see separate metric rows for each tagged endpoint in the output.
  4. Add an http.batch() call that fetches /posts/1, /users/1, and /albums/1 in parallel. Compare iteration_duration p(95) with and without batching.

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