Safeguard
Application Security•

How do you build a mock API server in JavaScript?

A mock API server in Node takes about twenty lines with the built-in http module. Use it for tests, keep it on localhost, and never put real data in it.

Safeguard Security Team
5 min read

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 listen without 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

  1. Start with the http module and canned JSON; add routes only for cases you test.
  2. Bind to 127.0.0.1 and use port 0 in tests.
  3. Include failure routes: 401, 404, 429, 500 and a timeout.
  4. Use hand-written fake data and fake tokens.
  5. Keep the mock out of build output and container images.
  6. 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.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.

Self-healing security runs on Safeguard.

Your first fix PR is minutes away.

No sales call required, even your agent can complete the purchase over MCP.