Swapping External Dependencies with Local Mocks Servers
The previous post introduced Postman Local Mock Servers, the Git-backed pattern that lets you stand a mock up next to your collections and environments, scaffold it from Agent Mode, and run it from Local View on the desktop app. That post covered how to create one. This post is about where a Local Mock earns its keep most quickly: in place of a paid, rate-limited third-party API.
The project is a train booking platform I’ve been building a Booking service that orchestrates the flow, plus Payments, Schedule, Notifications, and a Weather service. The Weather service is small but useful: when a passenger books a journey to Edinburgh, it checks the conditions at the destination on the day of travel and, if things look bad enough, fires a notification through the notifications service: “Severe weather – check for service disruption before travelling.”
Internally, the Weather service calls the OpenWeather API. That call is exactly the kind of dependency that benefits from a local swap. The free tier has rate limits, the paid tier costs real money per call, and a team of ten developers each running the full stack on a feature branch can chew through quota before anyone has reviewed a PR. Worse, the scenario I actually want to test, snow at Edinburgh triggers a travel warning, depends on the weather in Edinburgh actually being snowy. That is not a reliable test harness.
So I gave the OpenWeather API a Local Mock. Here’s what that looked like, end to end.
The seam is a single config value
The weather service exposes /weather?city=... to the rest of the platform. Internally, that route calls OpenWeather:
// GET /weather?city=London
app.get('/weather', async (req, res) => {
const { city } = req.query;
const params = new URLSearchParams({
q: city,
appid: OPENWEATHER_API_KEY,
units: 'metric',
});
const upstreamUrl = `${openWeatherApiUrl}/data/2.5/weather?${params}`;
const upstream = await fetch(upstreamUrl);
// ...normalise and return
});
The whole swap hinges on openWeatherApiUrl. In production it resolves to https://api.openweathermap.org. Locally, I want it to resolve to http://localhost:4504 and hit a mock that speaks OpenWeather’s contract. Nothing else in the weather service changes, same paths, same params, same normalisation, same response to downstream consumers like the booking service.
Capture the real call once
Before writing a line of mock code, I saved a real OpenWeather request to the project’s Postman collection:
GET /data/2.5/weather?q=London&appid={{OPENWEATHER_API_KEY}}&units=metric
Host: api.openweathermap.org
One representative 200 response body went with it. That captured request became the spec the mock has to honour. If the mock answers that exact path with a body of that exact shape, the weather service has no way to tell it apart from the real thing.
Build the mock from the captured call
Postman Local Mocks live under postman/mocks in the repo, right next to the collections. I used Agent Mode to scaffold a Node handler from the captured request, then tightened it until the contract matched. The relevant part:
// GET /data/2.5/weather
if (method === "GET" && pathname === "/data/2.5/weather") {
const q = parsedUrl.searchParams.get("q");
const appid = parsedUrl.searchParams.get("appid");
if (!appid) {
res.writeHead(401, { "Content-Type": "application/json" });
res.end(JSON.stringify({ cod: 401, message: "Invalid API key." }));
return;
}
const cityKey = q.split(",")[0].trim().toLowerCase();
const match = cityWeather[cityKey];
if (!match) {
res.writeHead(404, { "Content-Type": "application/json" });
res.end(JSON.stringify({ cod: "404", message: "city not found" }));
return;
}
res.writeHead(200, { "Content-Type": "application/json" });
res.end(JSON.stringify({ ...match, dt: Math.floor(Date.now() / 1000) }));
}

The interesting decisions are all about what the mock deliberately returns:
-
London → thunderstorm with heavy rain, wind 17.5 m/s
-
Edinburgh → light snow, 1.5°C
-
Manchester → light rain
-
Bristol → clear sky, 14.8°C
Different cities return different conditions on purpose so when I demo or test the booking flow, I can pick a destination and know exactly which branch of the alerting logic I’m exercising. Edinburgh is my “warning” path. Bristol is my “no alert” path. Neither depends on the actual sky.
The failure modes matter too. A missing appid returns OpenWeather’s real 401 shape; an unknown city returns its 404 shape. The weather service’s error handling gets exercised by the mock instead of being theoretical.
Two npm scripts, one decision
Wiring it into the dev loop is a matter of env vars:
{
"scripts": {
"start:dev": "concurrently -n booking,weather,notification,payment",
"dev:mock:openweather": "OPENWEATHER_API_URL=http://localhost:4504 npm run --silent dev"
}
}
npm run start:dev points at the real OpenWeather and needs a real key in .env. npm run dev:mock:openweather boots the local mock first and overrides OPENWEATHER_API_URL so the weather service resolves to localhost. The mock config is checked in, so anyone cloning the repo gets the mocked variant for free, no key, no quota, no shared sandbox.
What the full flow looks like
With the mock running, booking a trip to Edinburgh produces a clean end-to-end story on a single laptop:
-
Booking service receives the reservation and calls the weather service for
city=Edinburgh. -
Weather service hits
http://localhost:4504/data/2.5/weather?q=Edinburgh&appid=.... -
Mock returns the snow payload.
-
Weather service normalises it, computes
travelAdvisory.level = 'warning', returns to the booking service. -
Booking service sees the warning and fires a notification through the (also mocked) notification service.

No third-party network calls. No API key required. Deterministic every run.
The same trick scales to any vendor
OpenWeather is the example here, but the shape of the work generalises. When the Payments service grows up to talk to a gateway like Stripe, the four steps will be the same as they were for the weather service: find the seam (one config value), capture the contract (one Postman request), mirror it (one mock file), wire it in (one npm script). Same for an SMS provider on the notifications side, a geocoding API, a feature-flag service, anything whose request and response shape can live in a Postman collection.
Payments is the one I’m most looking forward to. The wins there are different from weather: instead of avoiding cost, you get deterministic failure scenarios (declined cards, refunds, edge cases) on demand, without juggling test card numbers or waiting on webhooks. When that integration lands in this repo, the mock will land alongside it.
The pattern, in one line
If a vendor’s request and response shape can be captured in Postman, it can be swapped for a local mock and the rest of your stack will never know the difference.
That is the trade I want every team on the booking platform making by default: real third-party calls when you’re testing the integration itself, and a local mock the rest of the time.

What do you think about this topic? Tell us in a comment below.