You can build a mock API server in JavaScript with Node's built-in http module and no dependencies: create a server, return canned JSON for known routes and a 404 for the rest. Bind it to localhost, use fake data only, and start it from your tests so each run is predictable.
Why mock an API?
A mock lets you develop or test a client without the real service. Tests run faster, work offline and can force rare cases such as a 500 error or a slow response. From a security view, a mock also keeps real credentials and production data out of test runs.
What is the smallest useful mock?
Node's http module provides createServer, which takes a request listener. This version needs Node 18 or later and uses the ES module syntax.
import http from 'node:http';
const users = [{ id: 1, name: 'Test User' }];
export function startMock(port = 0) {
const server = http.createServer((req, res) => {
const url = new URL(req.url, 'http://localhost');
res.setHeader('content-type', 'application/json');
if (req.method === 'GET' && url.pathname === '/users') {
res.end(JSON.stringify(users));
} else if (req.method === 'GET' && url.pathname === '/fail') {
res.statusCode = 500;
res.end(JSON.stringify({ error: 'simulated failure' }));
} else {
res.statusCode = 404;
res.end(JSON.stringify({ error: 'not found' }));
}
});
return new Promise((resolve) => {
server.listen(port, '127.0.0.1', () => resolve(server));
});
}
Passing port 0 lets the operating system pick a free port, so parallel test runs do not collide. Read the real port from server.address().port.
How do you use it in a test?
Node ships a test runner, node:test. Start the mock in a setup step, point your client at its address, assert, then close the server. Because the mock returns fixed data, a failing test points at your client, not at a moving service.
Add routes for the cases you most worry about: an empty list, a malformed body, a 401, a 429 and a response that is slow. A client that has only seen success is untested.
What are the security pitfalls?
- Listening on all interfaces. Binding to 127.0.0.1 keeps the mock from being reachable by other machines on the network. The default for
listenwithout a host can accept outside connections. - Real data in fixtures. Copying a production response into a fixture is a common leak. Write fake data by hand, or scrub copies.
- Real secrets. Use obviously fake tokens. A mock that checks for a hard-coded key tempts someone to reuse a real one.
- Mock left running or deployed. Keep it in a test folder, and make sure your build does not ship it. A stray mock in a staging image that always answers "authorized" is a real hazard.
- Open CORS. If you add permissive cross-origin headers for convenience, keep them out of anything reachable beyond localhost.
When should you use a library instead?
For many routes, request matching and response sequences, a mocking library saves time. Choose one with active maintenance and check its dependencies before adding it. The standard library version above is a good start because it adds nothing to your dependency tree.
A constructed example: a team builds a client for a payments API. Their mock returns a 429 on every third call. The first run shows the client retries instantly and floods the service. They add exponential backoff and a cap on retries, then keep the mock route as a regression test.
Safeguard's dependency scanning can check what a mocking library pulls in before it lands in your lockfile.
How do you simulate slow or flaky behavior?
Real services are slow sometimes, and a mock that always answers instantly hides timeout bugs. Add a route that waits before it responds, using a timer inside the handler, and another that closes the connection without a reply. Your client should have a timeout, a retry limit and a clear error for each. Run these cases in every test pass, because a client that hangs forever on a stalled connection is a denial of service waiting to happen in production.
How to check or apply this
- Start with the http module and canned JSON; add routes only for cases you test.
- Bind to 127.0.0.1 and use port 0 in tests.
- Include failure routes: 401, 404, 429, 500 and a timeout.
- Use hand-written fake data and fake tokens.
- Keep the mock out of build output and container images.
- If you adopt a library, review its maintenance and its dependencies first.
Quick check
Is a mock server safe to expose on a network?
Not by default. Bind it to localhost and use it only in development and tests.
Sources
Last reviewed: 18 October 2026.