# Swapping External Dependencies with Local Mocks Servers

The [previous post](https://blog.postman.com/how-to-mock-apis-locally-during-development/) 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](https://openweathermap.org/api/current?collection=current_forecast) a Local Mock. Here's what that looked like, end to end.

[![](https://blog.postman.com/wp-content/uploads/2026/05/Local_Mocks_Arch-09f6cb.svg)](https://blog.postman.com/wp-content/uploads/2026/05/Local_Mocks_Arch-09f6cb.svg)

## 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) }));
}
```

![](https://blog.postman.com/wp-content/uploads/2026/05/Screenshot-2026-05-15-at-17.03.21-scaled.png)

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:

1. Booking service receives the reservation and calls the weather service for `city=Edinburgh`.
2. Weather service hits `http://localhost:4504/data/2.5/weather?q=Edinburgh&appid=...`.
3. Mock returns the snow payload.
4. Weather service normalises it, computes `travelAdvisory.level = 'warning'`, returns to the booking service.
5. Booking service sees the warning and fires a notification through the (also mocked) notification service.
 
 ![](https://blog.postman.com/wp-content/uploads/2026/05/Screenshot-2026-05-15-at-16.41.14.png)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.