Loading
October 26, 2026

AmirHesamTech

Raspberry Pi, homelab, Linux and automation — projects and practical guides

Postman API automation against a local server

Learning API testing has a chicken-and-egg problem: you need an API you’re allowed to change. Public practice APIs are mostly read-only, so you can send a GET and little else. The moment you want to create, update and delete records, you need your own.

That’s what my Postman automation guide on GitHub sets up: a small “heroes” API running on your own machine, and a Postman collection that exercises all five methods — GET, POST, PUT, PATCH and DELETE — with real assertions. Because the data is yours, you can break it and start over whenever you like.

Step 1: Start the local API

The server is a tiny Express app, nodejs-api-server. With Node.js installed:

Advertisement
git clone https://github.com/MRSamBadiei/nodejs-api-server.git
cd nodejs-api-server
npm i
npm run dev        # prints "Running on 3000" when it's ready

It’s now answering at http://localhost:3000, and it keeps its records in data.json — copy that file somewhere first so you can reset the data later. Port 3000 already taken? On macOS or Linux run PORT=3001 npm run dev; on Windows, change PORT=3000 in the dev script in package.json.

What the API does

Knowing exactly what the server returns is what lets you write real assertions. These come straight from its code:

Request Success When it fails
GET /heros 200 + the list of heroes —
GET /heros/:id 200 + that hero 404 Hero not found
POST /addHero 201 Your hero successfully added to the database. 400 if age isn’t a number, gender isn’t male/female, or name is empty
PATCH /heros/:id 200 Successfuly updated. 400 id does not exists
PUT /heros/:id 204 (no body) if the hero exists, 201 if it creates one 400 on invalid data
DELETE /heros/:id 200 Successfuly deleted. 400 id does not exists

Spot anything? A missing hero is a 404 on GET but a 400 on DELETE, and the messages spell “Successfuly” with one L. Inconsistencies like these are exactly what API tests exist to catch — and your assertions have to match the real text, typos included.

Step 2: Set up the collection

  1. In Postman create a workspace (for example MyAutomationAPI) and a collection (APIPractice).
  2. Open the collection’s Variables tab and add heroUrl with the value http://localhost:3000. Every request then starts with {{heroUrl}}, so moving to another port or server is a one-line change.

Postman has five kinds of variables — global, collection, environment, data and local. Collection variables are the right home for a base URL; globals are handy for values that one request passes to the next, as below.

Step 3: GET — your first tests

Create GET {{heroUrl}}/heros. A request only becomes a test when you check the response, so add these in the request’s Tests tab (newer Postman versions call it Scripts → Post-response):

pm.test("Status code is 200", function () {
    pm.response.to.have.status(200);
});
pm.test("Content-Type is JSON", function () {
    pm.response.to.have.header("Content-Type", "application/json; charset=utf-8");
});
pm.test("Date header is present", function () {
    pm.response.to.have.header("Date");
});
pm.test("Returns a list", function () {
    pm.expect(pm.response.json()).to.be.an("array");
});

Click Send and the Test Results tab shows each check passing or failing.

Step 4: POST with a random name — and prove it was saved

Hard-coding “sam” in every POST hides bugs and makes reruns collide. Postman can generate data with {{$randomFirstName}}, but then your test doesn’t know what name was sent. The fix is a pre-request script (Scripts → Pre-request) that picks the name first and stores it:

const name = pm.variables.replaceIn('{{$randomFirstName}}');
pm.globals.set("HERO_NAME", name);

Create POST {{heroUrl}}/addHero with this raw JSON body:

{ "name": "{{HERO_NAME}}", "age": 30, "gender": "female" }

and these tests:

pm.test("Status code is 201", function () {
    pm.response.to.have.status(201);
});
pm.test("Success message", function () {
    pm.expect(pm.response.json().msg).to.eql("Your hero successfully added to the database.");
});

The order of events for that request is: pre-request script → request → response → tests. That’s why the name is known before the test reads it.

This server’s POST answers with a message only, not the new record. So add one more request, GET {{heroUrl}}/heros, that finds the hero you just created and remembers its id for the next steps:

const hero = pm.response.json().find(h => h.name === pm.globals.get("HERO_NAME"));
pm.test("The new hero is in the list", function () {
    pm.expect(hero).to.not.be.undefined;
});
pm.globals.set("HERO_ID", hero.id);

Step 5: PATCH, PUT and DELETE

  • PATCH {{heroUrl}}/heros/{{HERO_ID}} with body { "age": 31 } — expect 200 and the message Successfuly updated.
  • PUT {{heroUrl}}/heros/{{HERO_ID}} with a full body — expect 204, which by definition has no body to check.
  • DELETE {{heroUrl}}/heros/{{HERO_ID}} — expect 200 and Successfuly deleted.

Then prove the delete really happened with one last GET {{heroUrl}}/heros/{{HERO_ID}}:

pm.test("The hero is gone", function () {
    pm.response.to.have.status(404);
    pm.expect(pm.response.json().error).to.eql("Hero not found");
});
pm.globals.unset("HERO_ID");     // tidy up for the next run

Now the collection creates, reads, changes and removes its own test data — so you can run it again and again.

Step 6: Run the whole collection from the command line and in CI

Inside Postman, the Runner plays the collection top to bottom. To run it without the app, export the collection and use Newman (or the Postman CLI):

npx newman run APIPractice.postman_collection.json

That same command is what a CI server runs on every push — start the API, run Newman, and fail the build if a test fails. Postman documents the Jenkins setup, and the same idea works in GitHub Actions.

Common problems

  • “Could not send request”. The server isn’t running, or it’s on a different port than heroUrl.
  • POST returns 400. Age must be a number and gender must be male or female; check the body is set to raw → JSON.
  • A message check fails on a correct response. Compare the text character by character — this server says “Successfuly”.
  • The data is a mess. Stop the server, restore your copy of data.json, start it again.

Frequently asked questions

Why test against a local server instead of a public API?

Because you can use every method, there are no rate limits or sign-ups, and you can reset the data whenever you want. It’s also how real teams test: against an environment they control.

Global, collection or environment variable?

Collection variables for settings that belong to the collection (the base URL), environment variables for values that change between dev, test and production, and globals or local variables for values passed between requests in one run.

Do I need a paid Postman plan?

No — everything here works on the free plan, and Newman is free and open source.

The original walkthrough, with screenshots of every step, is on GitHub. If you want UI and API tests in Java instead, see my ready-made Java test automation framework.

Advertisement

65 Posts

SDET and QA engineer. I build Raspberry Pi servers, a Proxmox homelab and automations, and share how on TikTok and Instagram (@techwithamirh3sam).

View All Posts

You Missed

REWARDS 🔰 LV1 0🪙 ❯
🔰
Recruit
Guest Operative
Level1
XP0 / 100
💬 Need help?
coins0
Streak0 days · best 0
📋 Daily missions
↻ Resets daily at midnight
Rank ladder
How to earn
✓ Welcome bonus for joining — +200 🪙 & +100 XP (one-time)
✓ Daily check-in (streak bonus) — +20 XP & +12–24 🪙 (more 🪙 the longer your streak)
✓ Spin the daily wheel — +10 to +150 🪙 (free, once a day)
✓ Daily missions — check-in +15 🪙/+10 XP · daily spin +15 🪙/+10 XP
✓ Finish ALL daily missions — +30 🪙 & +20 XP bonus
✓ Level up — +25 🪙 every new level
✓ Achievements — first sign-in +50 🪙 · Lv5 +75 · Lv10 +150 · Lv25 +300 · 7-day streak +100 · 30-day +250 · collector +100 · subscriber +50
✓ Complete your profile (name, bio, photo) — up to +55 XP (one-time)
✓ Subscribe to the newsletter — +250 🪙 (one-time)
Coins and XP are just for fun: they have no cash value and can't be exchanged for money or prizes. You never earn them for viewing or clicking ads, or for how many pages or articles you open.
🛒 Token Shop0 🪙
🛍️ Shop0items
🎒 Inventory0items

🎡 Daily Spin

One free spin every day — pure bonus coins!
🪙
💬Need help?