npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@zze/mock-server

v0.5.0

Published

A reusable Node.js mock server for microfrontend development

Readme

@zze/mock-server

A reusable Node.js mock server for simulating backend APIs during local microfrontend development.

Features

  • 🚀 Zero-config TypeScript support - Write configs in JSON, JS, or TS
  • 🔄 Hot reload - Automatic config reloading on file changes
  • 🎭 Scenarios - Define multiple response scenarios per endpoint
  • 🌊 Flows - Activate groups of scenarios to simulate user journeys
  • 📁 File download - Serve binary files (PDFs, images, CSVs) from scenarios
  • 📤 File upload - Accept multipart/form-data upload requests out of the box
  • 🎨 Admin UI - Visual dashboard at /mock-admin to control the server
  • 🔌 REST API - Programmatic control via admin endpoints
  • 📦 Single package - Everything bundled, no external dependencies needed

Installation

npm install --save-dev @zze/mock-server

Quick Start

1. Create your config directory structure

your-project/
├── mock-config/
│   ├── endpoints/
│   │   └── users.json
│   └── flows/
│       └── error-flow.json
└── package.json

2. Define an endpoint

mock-config/endpoints/users.json

{
  "id": "get-users",
  "path": "/users",
  "method": "GET",
  "defaultScenarioId": "success",
  "scenarios": [
    {
      "id": "success",
      "name": "Success Response",
      "status": 200,
      "body": [
        { "id": 1, "name": "Alice" },
        { "id": 2, "name": "Bob" }
      ]
    },
    {
      "id": "empty",
      "name": "Empty List",
      "status": 200,
      "body": []
    },
    {
      "id": "error",
      "name": "Server Error",
      "status": 500,
      "body": { "error": "Internal server error" }
    }
  ]
}

3. Start the server

Option A: Programmatic (recommended)

// scripts/mock-server.ts
import { MockServer } from '@zze/mock-server';

const server = new MockServer({
  configPath: './mock-config',
  port: 3001,
  apiPrefix: '/api',
  hotReload: true
});

await server.start();
console.log('🎭 Mock server running at http://localhost:3001');
console.log('📊 Admin UI at http://localhost:3001/mock-admin');

Run with:

npx tsx scripts/mock-server.ts

Option B: Simple script in package.json

{
  "scripts": {
    "mock": "tsx scripts/mock-server.ts"
  }
}

4. Make requests

# Default scenario
curl http://localhost:3001/api/users
# → [{"id":1,"name":"Alice"},{"id":2,"name":"Bob"}]

# Open admin UI to switch scenarios
open http://localhost:3001/mock-admin

Configuration

Endpoint Config

Each endpoint file defines an API endpoint with multiple response scenarios.

interface EndpointConfig {
  id: string;                    // Unique identifier
  path: string;                  // URL path (supports Express patterns)
  method: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH';
  defaultScenarioId: string;     // ID of the default scenario
  scenarios: ScenarioConfig[];   // Available response scenarios
}

Scenario Config

interface ScenarioConfig {
  id: string;                    // Unique within endpoint
  name: string;                  // Display name in admin UI
  status: number;                // HTTP status code (100-599)
  body?: any;                    // Response body (JSON or function) — mutually exclusive with file
  file?: string;                 // File path relative to configPath — mutually exclusive with body
  headers?: Record<string, string>;  // Custom response headers
  delay?: number;                // Response delay in ms
}

Note: body and file are mutually exclusive. Setting both will cause a validation error at startup.

Flow Config

Flows activate multiple scenarios at once to simulate complex user journeys.

interface FlowConfig {
  id: string;                    // Unique identifier
  name: string;                  // Display name
  description?: string;          // Optional description
  category?: string;             // Optional category for grouping in UI
  endpointScenarios: {           // Map of endpointId → scenarioId
    [endpointId: string]: string;
  };
}

Example: mock-config/flows/error-flow.json

{
  "id": "all-errors",
  "name": "All Errors Flow",
  "description": "Returns errors for all endpoints",
  "category": "Error States",
  "endpointScenarios": {
    "get-users": "error",
    "get-products": "error",
    "create-order": "validation-error"
  }
}

File Download

A scenario can serve a file instead of JSON by specifying a file path relative to configPath:

mock-config/endpoints/reports.json

{
  "id": "get-report",
  "path": "/reports/:id",
  "method": "GET",
  "defaultScenarioId": "pdf",
  "scenarios": [
    {
      "id": "pdf",
      "name": "PDF Report",
      "status": 200,
      "file": "./assets/sample-report.pdf",
      "headers": { "Content-Disposition": "attachment; filename=report.pdf" }
    },
    {
      "id": "not-found",
      "name": "Not Found",
      "status": 404,
      "body": { "error": "Report not found" }
    }
  ]
}

Place the file at mock-config/assets/sample-report.pdf. The Content-Type header is inferred automatically from the file extension if not set explicitly.

body and file are mutually exclusive — setting both causes a validation error at startup.


File Upload

Upload endpoints work out of the box — no special configuration needed. The server accepts multipart/form-data requests, discards the uploaded files, and returns the active scenario response:

mock-config/endpoints/avatar.json

{
  "id": "upload-avatar",
  "path": "/users/:id/avatar",
  "method": "POST",
  "defaultScenarioId": "success",
  "scenarios": [
    {
      "id": "success",
      "name": "Upload OK",
      "status": 200,
      "body": { "url": "/cdn/avatar.jpg" }
    },
    {
      "id": "too-large",
      "name": "File Too Large",
      "status": 413,
      "body": { "error": "File exceeds maximum size" }
    }
  ]
}

Global Delay

Add a baseline latency to every response without modifying individual scenarios:

const server = new MockServer({
  configPath: './mock-config',
  globalDelay: 200,             // every response waits 200 ms
});

Or use a random range to simulate variable network conditions:

const server = new MockServer({
  configPath: './mock-config',
  globalDelay: [100, 500],      // each response waits a random 100–500 ms
});

Priority: a scenario's own delay field always takes precedence. globalDelay is only used when the resolved scenario has no delay set.

{
  "id": "fast-scenario",
  "name": "Always fast",
  "status": 200,
  "delay": 0,
  "body": { "ok": true }
}

In the example above, delay: 0 overrides any globalDelay — the response is immediate.


TypeScript Configs

You can write configs in TypeScript for type safety and dynamic responses:

mock-config/endpoints/users.ts

import type { EndpointConfig } from '@zze/mock-server';

const endpoint: EndpointConfig = {
  id: 'get-users',
  path: '/users',
  method: 'GET',
  defaultScenarioId: 'success',
  scenarios: [
    {
      id: 'success',
      name: 'Success',
      status: 200,
      body: (req) => {
        // Dynamic response based on request
        const limit = parseInt(req.query.limit as string) || 10;
        return Array.from({ length: limit }, (_, i) => ({
          id: i + 1,
          name: `User ${i + 1}`,
          timestamp: new Date().toISOString()
        }));
      }
    }
  ]
};

export default endpoint;

Admin UI

The built-in admin UI is available at /mock-admin and provides:

  • Endpoints Panel: View all endpoints, expand to see scenarios, click to activate
  • Flows Panel: View and activate user flows with category grouping
    • Create new flows with the endpoint/scenario picker
    • Edit or duplicate existing flows
    • Delete flows with confirmation
    • Collapsible category groups for organization
    • Search/filter flows by name
  • State Panel: See current active flow and scenario count
  • Controls: Refresh data, reset to defaults

Admin UI Screenshot


Admin REST API

Control the mock server programmatically:

State & Config

| Method | Endpoint | Description | |--------|----------|-------------| | GET | /mock-admin/api/config | Get all endpoints and flows | | GET | /mock-admin/api/state | Get current active state | | POST | /mock-admin/api/scenarios/activate | Activate a scenario | | POST | /mock-admin/api/flows/activate | Activate/deactivate a flow | | POST | /mock-admin/api/reset | Reset to default state |

Flow CRUD

| Method | Endpoint | Description | |--------|----------|-------------| | POST | /mock-admin/api/flows | Create a new flow | | PUT | /mock-admin/api/flows/:id | Update an existing flow | | DELETE | /mock-admin/api/flows/:id | Delete a flow | | POST | /mock-admin/api/flows/:id/duplicate | Duplicate an existing flow |

Examples

# Get current config
curl http://localhost:3001/mock-admin/api/config

# Activate a scenario
curl -X POST http://localhost:3001/mock-admin/api/scenarios/activate \
  -H "Content-Type: application/json" \
  -d '{"endpointId": "get-users", "scenarioId": "error"}'

# Activate a flow
curl -X POST http://localhost:3001/mock-admin/api/flows/activate \
  -H "Content-Type: application/json" \
  -d '{"flowId": "all-errors"}'

# Deactivate flow
curl -X POST http://localhost:3001/mock-admin/api/flows/activate \
  -H "Content-Type: application/json" \
  -d '{"flowId": null}'

# Reset to defaults
curl -X POST http://localhost:3001/mock-admin/api/reset

# Create a new flow
curl -X POST http://localhost:3001/mock-admin/api/flows \
  -H "Content-Type: application/json" \
  -d '{"flow": {"id": "my-flow", "name": "My Flow", "category": "Testing", "endpointScenarios": {"get-users": "error"}}}'

# Update a flow
curl -X PUT http://localhost:3001/mock-admin/api/flows/my-flow \
  -H "Content-Type: application/json" \
  -d '{"flow": {"id": "my-flow", "name": "My Updated Flow", "category": "Testing", "endpointScenarios": {"get-users": "success"}}}'

# Duplicate a flow
curl -X POST http://localhost:3001/mock-admin/api/flows/my-flow/duplicate \
  -H "Content-Type: application/json" \
  -d '{"newId": "my-flow-copy"}'

# Delete a flow
curl -X DELETE http://localhost:3001/mock-admin/api/flows/my-flow

Programmatic API

MockServer Class

const server = new MockServer({
  configPath: './mock-config',
  port: 3001,                   // Default: 3001
  apiPrefix: '/api',            // Default: '/api'
  hotReload: true,              // Default: true
  globalDelay: 200              // Optional: add 200 ms to every response (unless scenario overrides)
  // globalDelay: [100, 500]   // Or a random range in ms
});

Lifecycle Methods

// Start the server
await server.start();

// Stop the server
await server.stop();

// Restart (stop + start)
await server.restart();

// Manually reload config
await server.reload();

State & Info

// Get server state
const state = server.getState();
// {
//   running: true,
//   port: 3001,
//   endpointCount: 5,
//   flowCount: 2,
//   hotReloadActive: true,
//   activeFlowId: null,
//   activeScenarios: { 'get-users': 'error' }
// }

// Get loaded endpoints
const endpoints = server.getEndpoints();

// Get loaded flows
const flows = server.getFlows();

// Get config path
const configPath = server.getConfigPath();

Control Methods

// Activate a specific scenario
server.activateScenario('get-users', 'error');

// Activate a flow
server.activateFlow('all-errors');

// Deactivate flow
server.activateFlow(null);

// Reset to defaults
server.reset();

Event Handlers

// Listen for config reloads
const unsubscribe = server.onReload((event) => {
  console.log('Config reloaded:', event.changedFiles);
});

// Listen for errors
server.onError((error) => {
  console.error('Server error:', error);
});

// Unsubscribe when done
unsubscribe();

Advanced: Access Express App

// Get the Express app instance (for custom middleware)
const app = server.getApp();
if (app) {
  app.use('/custom', customMiddleware);
}

// Get the HTTP server (for WebSocket, etc.)
const httpServer = server.getHttpServer();

Path Parameters & Patterns

Endpoints support Express-style path patterns:

{
  "id": "get-user-by-id",
  "path": "/users/:id",
  "method": "GET",
  "defaultScenarioId": "success",
  "scenarios": [
    {
      "id": "success",
      "name": "User Found",
      "status": 200,
      "body": { "id": 1, "name": "Alice" }
    },
    {
      "id": "not-found",
      "name": "Not Found",
      "status": 404,
      "body": { "error": "User not found" }
    }
  ]
}

Dynamic response using path params (TypeScript):

{
  id: 'success',
  name: 'User Found',
  status: 200,
  body: (req) => ({
    id: parseInt(req.params.id),
    name: `User ${req.params.id}`
  })
}

Response Delays

Simulate network latency:

{
  "id": "slow-response",
  "name": "Slow Response",
  "status": 200,
  "body": { "data": "loaded" },
  "delay": 2000
}

Custom Headers

Add custom response headers:

{
  "id": "with-headers",
  "name": "With Custom Headers",
  "status": 200,
  "headers": {
    "X-Custom-Header": "custom-value",
    "Cache-Control": "no-cache"
  },
  "body": { "data": "ok" }
}

Hot Reload

When hotReload: true (default), the server watches your config directory and automatically reloads when files change:

  • Add/remove endpoint files
  • Modify scenarios
  • Add/remove flows

Changes take effect immediately without restarting the server. A 300ms debounce prevents rapid reloads during saves.


Integration with Tests

Use the mock server in your test setup:

// test/setup.ts
import { MockServer } from '@zze/mock-server';

let server: MockServer;

beforeAll(async () => {
  server = new MockServer({
    configPath: './test/mock-config',
    port: 3099,
    hotReload: false
  });
  await server.start();
});

afterAll(async () => {
  await server.stop();
});

beforeEach(() => {
  server.reset();
});

// In tests
it('handles error response', async () => {
  server.activateScenario('get-users', 'error');
  
  const response = await fetch('http://localhost:3099/api/users');
  expect(response.status).toBe(500);
});

Troubleshooting

Port already in use

Error: listen EADDRINUSE: address already in use :::3001

Change the port or kill the existing process:

lsof -i :3001 | grep LISTEN | awk '{print $2}' | xargs kill

Config not loading

  • Ensure configPath points to a directory with endpoints/ and flows/ subdirectories
  • Check file extensions: .json, .js, or .ts
  • Validate JSON syntax
  • Check for required fields: id, path, method, defaultScenarioId, scenarios

TypeScript configs not working

Ensure you have typescript installed. The package uses jiti for zero-config TS loading.


API Reference

Types

// Main options
interface MockServerOptions {
  configPath: string;
  port?: number;
  apiPrefix?: string;
  hotReload?: boolean;
  watcherOptions?: WatcherOptions;
  /**
   * Global delay fallback for all responses.
   * Scenario-level `delay` always overrides this.
   * A number is a fixed ms value; a tuple is a [min, max] random range.
   */
  globalDelay?: number | [number, number];
}

// Server state
interface MockServerState {
  running: boolean;
  port: number | null;
  endpointCount: number;
  flowCount: number;
  hotReloadActive: boolean;
  activeFlowId: string | null;
  activeScenarios: Record<string, string | null>;
}

// Reload event
interface ReloadEvent {
  changedFiles: WatcherEvent[];
  timestamp: Date;
}

Exports

// Main class
export { MockServer, createMockServer };

// Types
export type {
  MockServerOptions,
  MockServerState,
  EndpointConfig,
  ScenarioConfig,
  FlowConfig,
  HttpMethod,
  ResponseBody,
  ServerOptions,
  ReloadEvent,
  WatcherOptions
};

// Constants
export { ADMIN_UI_PATH }; // '/mock-admin'

License

MIT