Skip to main content

Multi-protocol API mocking made simple - REST, GraphQL, WebSocket, UDP from a single JSON config

Project description

API Simulator

Python License Version

Multi-protocol API mocking made simple • REST • GraphQL • WebSocket • UDP

FeaturesQuick StartDocumentationExamplesContributing


🎯 Overview

API Simulator spins up lifelike backends from a single JSON configuration file. Perfect for frontend development, integration testing, and API prototyping without waiting for backend implementation.

Key Features

  • 🚀 Zero dependencies - Just Python and a JSON config
  • 🔌 Multi-protocol - REST, GraphQL, WebSocket, and UDP from one server
  • 📝 Request-aware templating - Dynamic responses based on headers, query params, and paths
  • 🎭 Realistic behavior - Rules, delays, errors, and chaos testing
  • 📊 Streaming support - SSE, NDJSON, and WebSocket broadcasts
  • 🔒 HTTPS/WSS ready - Built-in TLS support
  • 📖 OpenAPI export - Auto-generated documentation

🚀 Quick Start

Installation

pip install -e .

Basic Usage

  1. Create a config file (config.json):
{
  "rest": {
    "port": 3000,
    "path": "/api",
    "apis": [
      {
        "method": "GET",
        "path": "/users/{user_id}",
        "response": {
          "id": "{{ path('user_id') }}",
          "name": "Test User",
          "created": "{{ timestamp() }}"
        }
      }
    ]
  }
}
  1. Start the simulator:
apisim run --config config.json
  1. Test your API:
curl http://localhost:3000/api/users/123

📚 Documentation

Table of Contents

🛠️ CLI Commands

Server Management

# Start server
apisim run --config config.json [options]
  --templates templates.json  # Template macros
  --seed 42                   # Deterministic randomness
  --log-level INFO           # Logging level
  --certfile cert.pem        # HTTPS/WSS certificate
  --keyfile key.pem          # HTTPS/WSS key

# Check running servers
apisim status

# Stop servers
apisim stop [--all]

# Validate configuration
apisim validate --config config.json

# Export OpenAPI spec
apisim openapi export --config config.json [--out openapi.json]

⚙️ Configuration

REST API

{
  "rest": {
    "port": 3000,
    "path": "/api/v1",
    "apis": [
      {
        "method": "GET",
        "path": "/products/{product_id}",
        "response": {...},      // JSON response
        "text": "plain text",   // Text response
        "file": "path/to/file", // File response
        "binary_b64": "...",    // Binary response
        "stream": {...},        // NDJSON streaming
        "sse": {...},          // Server-Sent Events
        "rules": {...}         // Conditional behavior
      }
    ]
  }
}

Streaming Responses

Server-Sent Events (SSE):

{
  "method": "GET",
  "path": "/events",
  "sse": {
    "interval": 1,
    "event": "update",
    "template": {"price": "{{ random_float(100,200,2) }}"},
    "count": 10  // null for infinite
  }
}

NDJSON Streaming:

{
  "method": "GET",
  "path": "/stream",
  "stream": {
    "interval": 0.5,
    "template": {"seq": "{{ counter('seq') }}"},
    "content_type": "application/x-ndjson"
  }
}

WebSocket

{
  "websocket": {
    "port": 9080,
    "path": "/ws",
    "apis": [
      {
        "path": "/events",
        "response": {"type": "ack"},
        "broadcast": {
          "interval": 5,
          "response": {"event": "{{ counter('events') }}"}
        },
        "rules": {
          "message.action == 'subscribe'": {
            "response": {"subscribed": true}
          }
        }
      }
    ]
  }
}

GraphQL

{
  "graphql": {
    "port": 3000,
    "path": "/graphql",
    "queries": [
      {
        "operationName": "GetUser",
        "response": {"user": {"id": "123", "name": "Alice"}}
      }
    ],
    "mutations": [
      {
        "operationName": "CreateUser",
        "response": {"success": true, "id": "{{ uuid4() }}"}
      }
    ],
    "subscriptions": [
      {
        "operationName": "UserUpdates",
        "interval": 2,
        "response": {"user": {"updated": "{{ timestamp() }}"}}
      }
    ]
  }
}

UDP Broadcasting

{
  "udp": {
    "host": "127.0.0.1",
    "port": 5001,
    "apis": [
      {
        "name": "telemetry",
        "broadcast": {
          "interval": 1,
          "response": {"temp": "{{ random_float(20,30,1) }}"}
        }
      }
    ]
  }
}

🎨 Templating

Built-in Functions

Function Description Example
{{ timestamp() }} ISO 8601 timestamp 2024-01-15T10:30:00Z
{{ unix_timestamp() }} Unix timestamp 1705315800
{{ uuid4() }} UUID v4 a3bb189e-8bf9-4c90-b8f0-d7d3e7e4d6c1
{{ random_int(min, max) }} Random integer {{ random_int(1, 100) }}
{{ random_float(min, max, decimals) }} Random float {{ random_float(0, 1, 2) }}
{{ counter(name, start, step) }} Auto-incrementing counter {{ counter('order', 1000, 1) }}
{{ choice([...]) }} Random selection {{ choice(['A', 'B', 'C']) }}

Request-Aware Templates

Access request data in responses:

{
  "response": {
    "path_param": "{{ path('user_id') }}",
    "query_param": "{{ query('search') }}",
    "header": "{{ header('Authorization') }}",
    "body_field": "{{ body('email') }}",
    "method": "{{ method() }}"
  }
}

Custom Macros

Define reusable templates in templates.json:

{
  "functions": {
    "order_id": "counter('order', 1000, 1)",
    "product": "choice(['Widget', 'Gadget', 'Tool'])",
    "price": "random_float(10.0, 100.0, 2)"
  }
}

🎯 Rules & Conditional Behavior

Rule Conditions

  • Probability: "0.1" (10% chance)
  • Path equality: "path.user_id == '123'"
  • Query params: "query.debug == 'true'"
  • Headers: "header.X-Test == 'yes'"
  • Body fields: "body.amount > 100"

Actions

{
  "rules": {
    "query.test == 'error'": {
      "status": 500,
      "response": {"error": "Test error"},
      "headers": {"X-Error": "true"}
    },
    "header.X-Slow == 'true'": {
      "delay": "2s"
    },
    "0.05": {
      "ignore": true  // 5% drop rate
    }
  }
}

🧪 Testing

Running Tests

# All tests
pytest tests/ -v

# Specific protocols
pytest tests/test_protocols.py::TestRESTProtocol -v
pytest tests/test_e2e_protocols.py -v

# With coverage
pytest tests/ --cov=api_simulator

Test Structure

tests/
├── test_cli.py           # CLI command tests
├── test_protocols.py     # Protocol unit tests
└── test_e2e_protocols.py # End-to-end with lifespan

📖 Examples

Complete E-Commerce API

See examples/config.json for a full example with:

  • Product catalog with search
  • Order streaming
  • Real-time price updates (SSE)
  • WebSocket order events
  • GraphQL customer queries
  • UDP inventory feeds

Testing Different Protocols

After starting the server with apisim run --config examples/config.json:

REST API Tests:

# Basic GET endpoint
curl http://localhost:3000/api/v1/status

# Path parameters, query strings, and headers
curl "http://localhost:3000/api/v1/products/PROD-123?q=search" \
  -H "X-Session-Id: test-session"

# SSE streaming (real-time price updates)
curl -N http://localhost:3000/api/v1/prices/sse

# NDJSON streaming 
curl -N http://localhost:3000/api/v1/stream/orders

GraphQL Tests:

# Query
curl -X POST http://localhost:3000/graphql \
  -H "Content-Type: application/json" \
  -d '{"operationName":"GetCustomer"}'

# Mutation
curl -X POST http://localhost:3000/graphql \
  -H "Content-Type: application/json" \
  -d '{"operationName":"CreateOrder"}'

# List available operations
curl http://localhost:3000/openapi.json | jq '.paths."/graphql"'

WebSocket Tests:

# Using wscat (npm install -g wscat)
wscat -c ws://localhost:9080/realtime/events
> {"action":"subscribe"}

# Using Python
python3 -c "
import asyncio, websockets, json
async def test():
    async with websockets.connect('ws://localhost:9080/realtime/events') as ws:
        await ws.send(json.dumps({'action': 'subscribe'}))
        print(await ws.recv())
asyncio.run(test())
"

# GraphQL Subscriptions
python3 -c "
import asyncio, websockets, json
async def test():
    async with websockets.connect('ws://localhost:9080/realtime/graphql') as ws:
        await ws.send(json.dumps({'type': 'connection_init'}))
        print(await ws.recv())
        await ws.send(json.dumps({'id': '1', 'type': 'subscribe', 
                                 'payload': {'operationName': 'PriceUpdates'}}))
        print(await ws.recv())
asyncio.run(test())
"

UDP Listener:

python3 -c "
import socket, struct, msgpack
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.bind(('127.0.0.1', 5001))
print('Listening on 127.0.0.1:5001...')
while True:
    pkt, _ = s.recvfrom(65536)
    magic, ts, n = struct.unpack('>IIH', pkt[:10])
    print(f'Magic: 0x{magic:X}, Payload:', msgpack.unpackb(pkt[10:], raw=False))
"

🔌 API Documentation

When running, access auto-generated API docs at:

🐳 Docker

FROM python:3.11-slim
WORKDIR /app
COPY . .
RUN pip install -e .
CMD ["apisim", "run", "--config", "/config/config.json"]
docker build -t api-simulator .
docker run -p 3000:3000 -p 9080:9080 \
  -v $(pwd)/examples:/config \
  api-simulator

🤝 Contributing

We welcome contributions! Please see CONTRIBUTING.md for guidelines.

Development Setup

# Clone repository
git clone https://github.com/yourusername/api-simulator
cd api-simulator

# Create virtual environment
python -m venv venv
source venv/bin/activate  # or `venv\Scripts\activate` on Windows

# Install with dev dependencies
pip install -e ".[test]"

# Run tests
pytest tests/ -v

# Check code style
ruff check src/

📝 License

MIT License - see LICENSE file for details.

🙏 Acknowledgments

Built with:

📮 Support


Made with ❤️ by the API Simulator Team

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

api_simulator-0.3.0.tar.gz (33.1 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

api_simulator-0.3.0-py3-none-any.whl (23.2 kB view details)

Uploaded Python 3

File details

Details for the file api_simulator-0.3.0.tar.gz.

File metadata

  • Download URL: api_simulator-0.3.0.tar.gz
  • Upload date:
  • Size: 33.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.11.13

File hashes

Hashes for api_simulator-0.3.0.tar.gz
Algorithm Hash digest
SHA256 1e8f6ab4a80a37891a19a19e0d56992be93ea889a81571b9d8866c2655b31997
MD5 0e6f8c170374aa496f62a1463445ab96
BLAKE2b-256 c1bdabbce2400e5d2e0f68d977075ac41c559c709b4994028a2994619147c2bb

See more details on using hashes here.

File details

Details for the file api_simulator-0.3.0-py3-none-any.whl.

File metadata

  • Download URL: api_simulator-0.3.0-py3-none-any.whl
  • Upload date:
  • Size: 23.2 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.11.13

File hashes

Hashes for api_simulator-0.3.0-py3-none-any.whl
Algorithm Hash digest
SHA256 035e4c6c02b6ccfcd461e4dda40094252f05dfc0a5220600ad0692d7cb47dbe0
MD5 f1a7e61f6918f5434777a1aa0a914e7f
BLAKE2b-256 09ea2c0e208f3684596b5de2a501e9860fdbae46b6159f04dc48d626b92a0e9b

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page