APIs · SDKs
Configuring API clients and SDKs
Keep base URLs out of code, point them at the real service, and add a check that stops example addresses from reaching production.
Guide · Testing
Code that points at a made-up address such as api.example-petstore.com still sends real requests, and they reach whoever owns that name. A mock API gives the same convenience without that risk: it runs on your own machine or in your tests, answers instantly, and always returns the data you expect.
example.com, such as api.example.com, do not resolve at all, so the code fails with a timeout or
DNS error instead of showing how it handles real answers.A mock API solves all three: it listens on localhost or inside the test process, and nothing leaves
your machine.
| Tool | Best for | Runs as |
|---|---|---|
| Prism | You have an OpenAPI description and want answers that follow it | Local server (Node.js or Docker), port 4010 |
| WireMock | Exact, recorded answers, error cases and delays for any language | Local server (Java or Docker), port 8080 |
| MSW | JavaScript and TypeScript: front-end development and unit tests | Inside the browser or the Node.js test process |
| json-server | A working REST API from one JSON file, for prototypes | Local server (Node.js), port 3000 |
For the Swagger Petstore sample API itself, run the official image locally; see Looking for the Swagger Petstore?
Prism reads an OpenAPI (or Swagger 2.0) description and answers every operation in it with the examples or schemas from that file. Requests that do not match the description get a clear validation error, which makes it useful for checking a client before the real API exists.
# With Node.js
npm install -g @stoplight/prism-cli
prism mock openapi.yaml
# With Docker
docker run --init --rm -v "$(pwd)":/tmp -p 4010:4010 stoplight/prism:5 mock -h 0.0.0.0 /tmp/openapi.yaml
# Then
curl http://127.0.0.1:4010/pets
curl http://127.0.0.1:4010/pets/1 -H "Prefer: code=404"
The Prefer header chooses a specific response from the description, such as an error code or a named
example. With --dynamic (-d) Prism generates fresh data from the schemas on every request.
WireMock answers from stub files that you write or record. It suits any language, and it can simulate slow responses and failures that a real service rarely produces on demand.
# mocks/mappings/pet.json
{
"request": { "method": "GET", "url": "/v1/pets/1" },
"response": {
"status": 200,
"headers": { "Content-Type": "application/json" },
"jsonBody": { "id": 1, "name": "Rex", "status": "available" }
}
}
# Start it with the folder that holds mappings/ (and __files/ for larger bodies)
docker run -it --rm -p 8080:8080 -v "$(pwd)/mocks":/home/wiremock wiremock/wiremock
curl http://localhost:8080/v1/pets/1
Add "fixedDelayMilliseconds": 3000 to a response to test timeouts, or a status such as 503
to test retries. The admin API at /__admin lets tests add stubs and check which requests arrived.
Mock Service Worker intercepts requests inside the application itself: in the browser through a service worker, in Node.js inside the test process. The code under test keeps calling its normal base URL; no extra server runs.
// handlers.js
import { http, HttpResponse } from 'msw'
export const handlers = [
http.get('https://api.example.com/v1/pets/:id', ({ params }) =>
HttpResponse.json({ id: Number(params.id), name: 'Rex', status: 'available' })),
http.post('https://api.example.com/v1/pets', () =>
HttpResponse.json({ error: 'name is required' }, { status: 400 })),
]
// In tests (Node.js)
import { setupServer } from 'msw/node'
const server = setupServer(...handlers)
beforeAll(() => server.listen({ onUnhandledRequest: 'error' }))
afterEach(() => server.resetHandlers())
afterAll(() => server.close())
onUnhandledRequest: 'error' makes a test fail as soon as code calls an address without a handler, so a
forgotten placeholder shows up in the test run instead of in production. In the browser, run
npx msw init public/ once and start the worker from msw/browser.
In PHP tests, Guzzle’s MockHandler does the same inside the test process, and Symfony has
MockHttpClient:
// PHP, Guzzle: answers in order, without a network
use GuzzleHttp\Client;
use GuzzleHttp\Handler\MockHandler;
use GuzzleHttp\HandlerStack;
use GuzzleHttp\Psr7\Response;
$mock = new MockHandler([
new Response(200, ['Content-Type' => 'application/json'], '{"id": 1, "name": "Rex", "status": "available"}'),
new Response(400, [], '{"error": "name is required"}'),
]);
$client = new Client(['handler' => HandlerStack::create($mock), 'base_uri' => 'https://api.example.com/v1/']);
// PHP, Symfony
$client = new Symfony\Component\HttpClient\MockHttpClient(
[new Symfony\Component\HttpClient\Response\MockResponse('{"id": 1, "name": "Rex"}')],
'https://api.example.com/v1/'
);
The address in the handlers is api.example.com: a reserved name that never reaches a real server, even
when a request slips past the mock.
json-server turns one JSON file into a REST API with list, detail, create, update and delete routes. Changes are written back to the file, which makes it handy for prototypes and demos.
# db.json
{
"pets": [ { "id": "1", "name": "Rex", "status": "available" } ],
"orders": []
}
npx json-server db.json
curl http://localhost:3000/pets
curl -X POST http://localhost:3000/orders -H "Content-Type: application/json" -d '{"petId": "1"}'
It has no validation or authentication, so keep it to prototypes; for contract tests, Prism or WireMock fit better.
Keep the base URL in configuration, with the mock as the value for development and tests, and the real address only where the application actually runs:
# .env.development
API_BASE_URL=http://localhost:4010
# .env.test
API_BASE_URL=http://localhost:8080
# production: set in the hosting environment, never in the repository
API_BASE_URL=https://api.your-real-service.com
More on this, including a check that stops example addresses from reaching production: Configuring API clients and SDKs.