Making GET, POST, PUT, DELETE Requests

9 min read

The API Testing Masterclass lesson on HTTP methods taught you what GET, POST, PUT, PATCH, and DELETE mean at the protocol level. This lesson is the Karate implementation of that knowledge: the exact syntax for each verb, how to set path segments and query parameters, how to chain multiple calls in one scenario, and how to pass values between them. By the end you'll have a complete CRUD flow running in a single feature file.

GET requests

A basic GET:

Scenario: Get a single user
  Given url baseUrl
  And path 'users', 1
  When method get
  Then status 200

path 'users', 1 appends segments to the base URL. Each comma-separated value becomes a path segment, URL-encoded automatically. The result: baseUrl + '/users/1'. Use this pattern instead of string concatenation — it handles special characters safely.

GET with query parameters:

Scenario: Search users by role
  Given url baseUrl
  And path 'users'
  And param role = 'admin'
  And param active = true
  And param page = 1
  When method get
  Then status 200

Each param call adds one query string key-value pair: ?role=admin&active=true&page=1. For multiple values on the same key (e.g., ?tag=java&tag=qa), use params with an array: And params { tag: ['java', 'qa'] }.

POST requests

A POST with a JSON body:

Scenario: Create a new user
  Given url baseUrl
  And path 'users'
  And request { name: 'Alice', email: 'alice@test.com', role: 'admin' }
  When method post
  Then status 201
  And match response.id == '#number'
  And match response.name == 'Alice'

The request keyword accepts an inline JSON object, a variable, or a file reference (request read('user.json')). Karate automatically sets Content-Type: application/json when the request body is a JSON object — no manual header required.

PUT and PATCH

PUT replaces the entire resource:

Scenario: Update a user's full record
  Given url baseUrl
  And path 'users', 1
  And request { name: 'Alice Smith', email: 'alice.smith@test.com', role: 'admin' }
  When method put
  Then status 200
  And match response.name == 'Alice Smith'

PATCH updates only the fields you send:

Scenario: Patch the user's role
  Given url baseUrl
  And path 'users', 1
  And request { role: 'viewer' }
  When method patch
  Then status 200
  And match response.role == 'viewer'

The distinction maps directly to what you learned in the API Testing Masterclass: PUT is idempotent and replaces the whole document; PATCH is a partial update. Karate treats them identically — method put and method patch just change the HTTP verb.

DELETE requests

Scenario: Delete a user
  Given url baseUrl
  And path 'users', 1
  When method delete
  Then status 204

DELETE typically has no request body and returns 204 No Content. The request keyword is omitted — Karate sends an empty body by default when request is absent.

Headers per request

To set a header on one specific request (not globally via Background):

And header Authorization = 'Bearer ' + authToken
And header X-Custom-Header = 'value'

If the header applies to all requests in a feature file, set it in Background. If it applies to the entire test run, set it in karate-config.js with configure headers.

Chaining calls with def

Real API workflows chain calls: create a resource, use its ID to fetch it, update it, delete it. Karate uses * def to store values between requests:

Scenario: Full CRUD flow
  # CREATE
  Given path 'users'
  And request { name: 'Test User', email: 'test@test.com', role: 'tester' }
  When method post
  Then status 201
  * def userId = response.id
 
  # READ
  Given path 'users', userId
  When method get
  Then status 200
  And match response.name == 'Test User'
 
  # UPDATE
  Given path 'users', userId
  And request { name: 'Updated User', email: 'test@test.com', role: 'tester' }
  When method put
  Then status 200
  And match response.name == 'Updated User'
 
  # DELETE
  Given path 'users', userId
  When method delete
  Then status 204

* def userId = response.id captures the id from the POST response. That userId variable is then available in every subsequent step of the same scenario. In Rest Assured you'd use extract().path("id") to do the same thing — in Karate it's one line.

The CRUD flow, visualised

⚠️ Common mistakes

  • Using url instead of path for sub-paths. After setting url baseUrl in Background, use path 'users' to append segments — not url baseUrl + '/users'. String-concatenating URLs works but bypasses Karate's URL encoding and looks like an antipattern in code review. Reserve url for when you need to override the base URL for a single request (e.g., calling an external service in one scenario).
  • Forgetting that def variables are scenario-scoped. A userId defined with * def in Scenario 1 is not available in Scenario 2. If you need the same resource across scenarios, either generate it in a Background call to a reusable feature or set it up per-scenario. Sharing mutable state between scenarios creates brittle tests that fail in random order.
  • Missing request on POST/PUT and wondering why the body is empty. If you omit And request {...} before method post, Karate sends an empty body. The server often returns a 400 Bad Request or ignores required fields. The fix is always the same: add the request step before method.

🎯 Practice task

Write a complete CRUD flow against JSONPlaceholder. 35–45 minutes.

  1. In users/users.feature, write a Background with url baseUrl pointing at https://jsonplaceholder.typicode.com.
  2. Write a GET scenario: path 'users', 1, assert status 200, assert response.id == 1 and response.name == '#string'.
  3. Write a GET with query parameters: path 'users', add param _limit = 3, assert status 200 and match response == '#array' and karate.sizeOf(response) == 3.
  4. Write a POST: send { name: 'Test', email: 'test@test.com', username: 'tester' } to path 'users', assert status 201, assert response.id == '#number'.
  5. Write the full CRUD chain in one scenario — POST to create, store the id, GET to read, PUT to update (assert the name changed), DELETE (assert 204). JSONPlaceholder doesn't persist data, but the HTTP responses are realistic.
  6. Negative case: write a scenario that GETs path 'users', 0 and asserts status 404. Confirm the test passes even though 404 is an error status — Karate does not auto-fail on non-2xx.
  7. Stretch: write a PATCH scenario that sends only { name: 'Patched Name' } and asserts status 200 and response.name == 'Patched Name'. Compare the request body size with the PUT scenario from step 5.

Next lesson: Karate's assertion system — match, match contains, fuzzy matchers, and match each for arrays.

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