36 KiB
description, shortcut
| description | shortcut |
|---|---|
| Create a mock API server for testing | mock |
Create Mock API Server
Generate production-grade mock API servers from OpenAPI specifications with realistic fake data, stateful operations, and customizable response scenarios. Perfect for frontend development, integration testing, and API prototyping without backend dependencies.
Design Decisions
This command generates mock servers using Express.js with faker-js for realistic data generation. The implementation prioritizes:
- OpenAPI-First Approach: Generates endpoints directly from OpenAPI/Swagger specs to ensure contract compliance
- Stateful Mode: Optional in-memory persistence allows testing CRUD workflows with realistic state management
- Scenario-Based Responses: Conditional logic based on request parameters enables testing edge cases
- Network Realism: Built-in latency simulation and error injection mimic production API behavior
Alternatives Considered:
- JSON Server (limited to simple CRUD, no OpenAPI support)
- Mockoon (GUI-based, not scriptable)
- WireMock (Java-based, heavier dependency)
- Prism (excellent OpenAPI support but less flexible for custom scenarios)
When to Use This Command
USE WHEN:
- Developing frontend features before backend APIs are ready
- Testing error handling and edge cases without affecting production
- Creating reproducible test scenarios for QA and CI/CD
- Demonstrating API functionality to stakeholders
- Validating OpenAPI specifications with working examples
- Load testing frontend without backend infrastructure
DON'T USE WHEN:
- You need real business logic or database interactions
- Testing actual backend performance (use staging environment)
- Security testing (mocks bypass authentication layers)
- Integration testing with third-party services
- Production traffic routing (mocks are development tools only)
Prerequisites
Required:
- Node.js 18+ installed
- OpenAPI 3.0+ specification file (YAML or JSON)
- Basic understanding of RESTful API concepts
- Port availability (default: 3000)
Optional:
- Docker for containerized deployment
- Postman/Insomnia for API testing
- curl or httpie for command-line testing
Install Dependencies:
npm install express @faker-js/faker swagger-parser cors
npm install --save-dev nodemon @types/express
Step-by-Step Process
Step 1: Prepare OpenAPI Specification
Ensure your OpenAPI spec is valid and contains response schemas with examples:
# Validate OpenAPI spec
npx swagger-cli validate openapi.yaml
Step 2: Generate Mock Server Structure
Command analyzes the OpenAPI spec and generates:
- Express route handlers for each endpoint
- Response schemas with faker.js mappings
- Middleware for CORS, logging, error handling
- Optional stateful storage layer
Step 3: Configure Mock Behavior
Customize mock server with:
- Response delay ranges (simulate network latency)
- Error injection rates (test error handling)
- Stateful mode (enable in-memory persistence)
- Custom scenario rules (conditional responses)
Step 4: Start Mock Server
Launch the generated server with hot-reload:
npm run dev # Development mode with nodemon
npm start # Production mode
Step 5: Test and Iterate
Verify mock endpoints match OpenAPI contract:
# Test generated endpoints
curl http://localhost:3000/api/users
curl -X POST http://localhost:3000/api/users -d '{"name":"John"}'
Output Format
The command generates a complete Node.js project:
mock-server/
├── server.js # Express server entry point
├── routes/ # Generated route handlers
│ ├── users.js # User resource endpoints
│ ├── products.js # Product resource endpoints
│ └── orders.js # Order resource endpoints
├── middleware/ # Request/response middleware
│ ├── cors.js # CORS configuration
│ ├── logger.js # Request logging
│ └── errorHandler.js # Error handling
├── data/ # Stateful mode storage
│ ├── store.js # In-memory data store
│ └── seed.js # Initial data seeding
├── scenarios/ # Custom response logic
│ └── conditionalLogic.js
├── config/
│ └── server.config.js # Server configuration
├── package.json # Dependencies and scripts
├── .env.example # Environment variables template
└── README.md # Generated documentation
Code Examples
Example 1: OpenAPI Spec-Based Mock Generation
Input OpenAPI Spec (openapi.yaml):
openapi: 3.0.0
info:
title: User API
version: 1.0.0
paths:
/api/users:
get:
summary: List all users
responses:
'200':
description: Successful response
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/User'
post:
summary: Create a user
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/UserInput'
responses:
'201':
description: User created
content:
application/json:
schema:
$ref: '#/components/schemas/User'
/api/users/{id}:
get:
summary: Get user by ID
parameters:
- name: id
in: path
required: true
schema:
type: string
responses:
'200':
description: User found
content:
application/json:
schema:
$ref: '#/components/schemas/User'
'404':
description: User not found
components:
schemas:
User:
type: object
properties:
id:
type: string
format: uuid
email:
type: string
format: email
firstName:
type: string
lastName:
type: string
avatar:
type: string
format: uri
createdAt:
type: string
format: date-time
UserInput:
type: object
required:
- email
- firstName
- lastName
properties:
email:
type: string
format: email
firstName:
type: string
lastName:
type: string
Command Usage:
# Generate mock server from OpenAPI spec
/create-mock-server \
--spec openapi.yaml \
--output ./mock-api \
--port 3000 \
--delay 100-300
Generated routes/users.js:
import express from 'express';
import { faker } from '@faker-js/faker';
import { applyDelay } from '../middleware/delay.js';
const router = express.Router();
// Generate realistic fake user
function generateUser() {
return {
id: faker.string.uuid(),
email: faker.internet.email(),
firstName: faker.person.firstName(),
lastName: faker.person.lastName(),
avatar: faker.image.avatar(),
createdAt: faker.date.past().toISOString()
};
}
// GET /api/users - List all users
router.get('/users', applyDelay, (req, res) => {
const count = parseInt(req.query.limit) || 10;
const users = Array.from({ length: count }, generateUser);
res.json({
data: users,
pagination: {
total: count,
page: 1,
limit: count
}
});
});
// POST /api/users - Create user
router.post('/users', applyDelay, (req, res) => {
const { email, firstName, lastName } = req.body;
// Validation
if (!email || !firstName || !lastName) {
return res.status(400).json({
error: 'Missing required fields',
required: ['email', 'firstName', 'lastName']
});
}
const newUser = {
id: faker.string.uuid(),
email,
firstName,
lastName,
avatar: faker.image.avatar(),
createdAt: new Date().toISOString()
};
res.status(201).json(newUser);
});
// GET /api/users/:id - Get user by ID
router.get('/users/:id', applyDelay, (req, res) => {
const { id } = req.params;
// Simulate 404 for specific IDs
if (id === '00000000-0000-0000-0000-000000000000') {
return res.status(404).json({
error: 'User not found',
id
});
}
const user = {
...generateUser(),
id // Use the requested ID
};
res.json(user);
});
export default router;
Generated server.js:
import express from 'express';
import cors from 'cors';
import { config } from './config/server.config.js';
import logger from './middleware/logger.js';
import errorHandler from './middleware/errorHandler.js';
import usersRouter from './routes/users.js';
const app = express();
// Middleware
app.use(cors());
app.use(express.json());
app.use(logger);
// Routes
app.use('/api', usersRouter);
// Health check
app.get('/health', (req, res) => {
res.json({ status: 'ok', timestamp: new Date().toISOString() });
});
// Error handling
app.use(errorHandler);
// Start server
const PORT = process.env.PORT || config.port;
app.listen(PORT, () => {
console.log(`Mock API server running on http://localhost:${PORT}`);
console.log(`API documentation: http://localhost:${PORT}/api-docs`);
});
Example 2: Stateful Mock Server with CRUD Operations
Command Usage:
# Generate stateful mock server with persistent data
/create-mock-server \
--spec openapi.yaml \
--output ./mock-api \
--stateful \
--seed 50 \
--port 3000
Generated data/store.js (In-Memory Database):
import { faker } from '@faker-js/faker';
class MockStore {
constructor() {
this.collections = {
users: new Map(),
products: new Map(),
orders: new Map()
};
this.idCounters = {
users: 1,
products: 1,
orders: 1
};
}
// Generic CRUD operations
create(collection, data) {
const id = data.id || String(this.idCounters[collection]++);
const record = {
...data,
id,
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString()
};
this.collections[collection].set(id, record);
return record;
}
findAll(collection, filters = {}) {
const records = Array.from(this.collections[collection].values());
// Apply filters
return records.filter(record => {
return Object.entries(filters).every(([key, value]) => {
if (value === undefined) return true;
return String(record[key]).toLowerCase().includes(String(value).toLowerCase());
});
});
}
findById(collection, id) {
return this.collections[collection].get(id) || null;
}
update(collection, id, data) {
const existing = this.findById(collection, id);
if (!existing) return null;
const updated = {
...existing,
...data,
id, // Prevent ID change
updatedAt: new Date().toISOString()
};
this.collections[collection].set(id, updated);
return updated;
}
delete(collection, id) {
const record = this.findById(collection, id);
if (!record) return false;
return this.collections[collection].delete(id);
}
count(collection) {
return this.collections[collection].size;
}
clear(collection) {
this.collections[collection].clear();
}
// Seed data
seed(collection, count, generator) {
for (let i = 0; i < count; i++) {
this.create(collection, generator());
}
}
}
// Singleton instance
export const store = new MockStore();
Generated data/seed.js:
import { faker } from '@faker-js/faker';
import { store } from './store.js';
export function seedDatabase() {
console.log('Seeding database...');
// Seed users
store.seed('users', 50, () => ({
email: faker.internet.email(),
firstName: faker.person.firstName(),
lastName: faker.person.lastName(),
avatar: faker.image.avatar(),
role: faker.helpers.arrayElement(['user', 'admin', 'moderator']),
status: faker.helpers.arrayElement(['active', 'inactive', 'suspended'])
}));
// Seed products
store.seed('products', 100, () => ({
name: faker.commerce.productName(),
description: faker.commerce.productDescription(),
price: parseFloat(faker.commerce.price()),
category: faker.commerce.department(),
inStock: faker.datatype.boolean(),
sku: faker.string.alphanumeric(8).toUpperCase(),
image: faker.image.urlLoremFlickr({ category: 'product' })
}));
// Seed orders
const users = store.findAll('users');
const products = store.findAll('products');
store.seed('orders', 200, () => {
const user = faker.helpers.arrayElement(users);
const orderItems = faker.helpers.arrayElements(products, { min: 1, max: 5 });
const total = orderItems.reduce((sum, item) => sum + item.price, 0);
return {
userId: user.id,
items: orderItems.map(item => ({
productId: item.id,
quantity: faker.number.int({ min: 1, max: 5 }),
price: item.price
})),
total: parseFloat(total.toFixed(2)),
status: faker.helpers.arrayElement(['pending', 'processing', 'shipped', 'delivered', 'cancelled']),
shippingAddress: {
street: faker.location.streetAddress(),
city: faker.location.city(),
state: faker.location.state(),
zip: faker.location.zipCode(),
country: faker.location.country()
}
};
});
console.log(`Seeded ${store.count('users')} users`);
console.log(`Seeded ${store.count('products')} products`);
console.log(`Seeded ${store.count('orders')} orders`);
}
Stateful routes/users.js:
import express from 'express';
import { store } from '../data/store.js';
import { applyDelay } from '../middleware/delay.js';
const router = express.Router();
// GET /api/users - List with filtering and pagination
router.get('/users', applyDelay, (req, res) => {
const { search, role, status, page = 1, limit = 20 } = req.query;
// Apply filters
const filters = {};
if (search) filters.firstName = search; // Partial match
if (role) filters.role = role;
if (status) filters.status = status;
const allUsers = store.findAll('users', filters);
// Pagination
const pageNum = parseInt(page);
const limitNum = parseInt(limit);
const startIndex = (pageNum - 1) * limitNum;
const endIndex = startIndex + limitNum;
const paginatedUsers = allUsers.slice(startIndex, endIndex);
res.json({
data: paginatedUsers,
pagination: {
total: allUsers.length,
page: pageNum,
limit: limitNum,
totalPages: Math.ceil(allUsers.length / limitNum)
}
});
});
// POST /api/users - Create with validation
router.post('/users', applyDelay, (req, res) => {
const { email, firstName, lastName, role = 'user' } = req.body;
if (!email || !firstName || !lastName) {
return res.status(400).json({
error: 'Validation failed',
details: {
email: !email ? 'Email is required' : null,
firstName: !firstName ? 'First name is required' : null,
lastName: !lastName ? 'Last name is required' : null
}
});
}
// Check for duplicate email
const existing = store.findAll('users').find(u => u.email === email);
if (existing) {
return res.status(409).json({
error: 'User with this email already exists',
existingId: existing.id
});
}
const newUser = store.create('users', {
email,
firstName,
lastName,
role,
status: 'active',
avatar: `https://api.dicebear.com/7.x/avataaars/svg?seed=${email}`
});
res.status(201).json(newUser);
});
// GET /api/users/:id - Get by ID
router.get('/users/:id', applyDelay, (req, res) => {
const user = store.findById('users', req.params.id);
if (!user) {
return res.status(404).json({
error: 'User not found',
id: req.params.id
});
}
res.json(user);
});
// PUT /api/users/:id - Update
router.put('/users/:id', applyDelay, (req, res) => {
const { email, firstName, lastName, role, status } = req.body;
const updated = store.update('users', req.params.id, {
email,
firstName,
lastName,
role,
status
});
if (!updated) {
return res.status(404).json({
error: 'User not found',
id: req.params.id
});
}
res.json(updated);
});
// DELETE /api/users/:id
router.delete('/users/:id', applyDelay, (req, res) => {
const deleted = store.delete('users', req.params.id);
if (!deleted) {
return res.status(404).json({
error: 'User not found',
id: req.params.id
});
}
res.status(204).send();
});
export default router;
Example 3: Dynamic Response Scenarios with Conditional Logic
Command Usage:
# Generate mock server with custom scenarios
/create-mock-server \
--spec openapi.yaml \
--output ./mock-api \
--scenarios \
--error-rate 5 \
--port 3000
Generated scenarios/conditionalLogic.js:
/**
* Scenario-based response logic for realistic API behavior
*/
// Simulate different response scenarios based on request data
export const scenarios = {
// Payment processing scenarios
payment: {
// Test card numbers trigger specific responses
testCards: {
'4242424242424242': { status: 'success', message: 'Payment approved' },
'4000000000000002': { status: 'declined', message: 'Card declined' },
'4000000000009995': { status: 'error', message: 'Insufficient funds' },
'4000000000000069': { status: 'error', message: 'Card expired' },
'4000000000000127': { status: 'error', message: 'Incorrect CVC' }
},
handler: (cardNumber, amount) => {
// Check test cards
if (scenarios.payment.testCards[cardNumber]) {
return scenarios.payment.testCards[cardNumber];
}
// Amount-based scenarios
if (amount > 10000) {
return {
status: 'pending',
message: 'Large transaction requires manual review',
reviewId: `REV-${Date.now()}`
};
}
// Random 5% failure rate for realistic testing
if (Math.random() < 0.05) {
return {
status: 'error',
message: 'Payment gateway timeout',
retryable: true
};
}
return {
status: 'success',
message: 'Payment approved',
transactionId: `TXN-${Date.now()}`
};
}
},
// Authentication scenarios
auth: {
// Test credentials
testUsers: {
'admin@example.com': { password: 'admin123', role: 'admin', mfaEnabled: true },
'user@example.com': { password: 'user123', role: 'user', mfaEnabled: false },
'locked@example.com': { password: 'locked123', locked: true },
'expired@example.com': { password: 'expired123', passwordExpired: true }
},
handler: (email, password) => {
const user = scenarios.auth.testUsers[email];
if (!user) {
return {
status: 401,
error: 'Invalid credentials'
};
}
if (user.locked) {
return {
status: 403,
error: 'Account locked due to multiple failed login attempts',
unlockTime: new Date(Date.now() + 3600000).toISOString()
};
}
if (user.passwordExpired) {
return {
status: 403,
error: 'Password expired',
passwordResetRequired: true
};
}
if (user.password !== password) {
return {
status: 401,
error: 'Invalid credentials',
attemptsRemaining: 3
};
}
if (user.mfaEnabled) {
return {
status: 200,
mfaRequired: true,
mfaToken: `MFA-${Date.now()}`
};
}
return {
status: 200,
token: `JWT-${Date.now()}`,
user: { email, role: user.role }
};
}
},
// Rate limiting scenarios
rateLimit: {
requestCounts: new Map(),
handler: (clientId, limit = 100, window = 60000) => {
const now = Date.now();
const key = `${clientId}-${Math.floor(now / window)}`;
const count = scenarios.rateLimit.requestCounts.get(key) || 0;
scenarios.rateLimit.requestCounts.set(key, count + 1);
// Clean old entries
for (const [k, v] of scenarios.rateLimit.requestCounts.entries()) {
if (k.split('-')[1] < Math.floor((now - window * 2) / window)) {
scenarios.rateLimit.requestCounts.delete(k);
}
}
if (count >= limit) {
return {
status: 429,
error: 'Rate limit exceeded',
retryAfter: Math.ceil((window - (now % window)) / 1000),
limit,
remaining: 0
};
}
return {
status: 200,
limit,
remaining: limit - count - 1,
reset: Math.floor((now + window) / 1000)
};
}
},
// Error injection for testing
errorInjection: {
handler: (errorRate = 0.05) => {
const rand = Math.random();
if (rand < errorRate * 0.2) {
return { status: 500, error: 'Internal server error' };
}
if (rand < errorRate * 0.4) {
return { status: 503, error: 'Service temporarily unavailable', retryAfter: 30 };
}
if (rand < errorRate * 0.6) {
return { status: 504, error: 'Gateway timeout' };
}
if (rand < errorRate) {
return { status: 502, error: 'Bad gateway' };
}
return null; // No error
}
},
// Conditional responses based on headers
headerBased: {
handler: (headers) => {
// API version handling
if (headers['api-version'] === '1.0') {
return { format: 'legacy', deprecationWarning: true };
}
// Feature flags
const features = (headers['x-features'] || '').split(',');
const enabledFeatures = {};
features.forEach(f => {
enabledFeatures[f.trim()] = true;
});
// A/B testing
const variant = headers['x-variant'] || 'control';
return {
format: 'current',
features: enabledFeatures,
variant
};
}
}
};
// Middleware to apply scenarios
export function applyScenarios(req, res, next) {
// Add scenario helpers to request
req.scenarios = scenarios;
// Add error injection
const injectedError = scenarios.errorInjection.handler(
parseFloat(process.env.ERROR_RATE) || 0.05
);
if (injectedError) {
return res.status(injectedError.status).json(injectedError);
}
// Add rate limiting
const clientId = req.ip || req.headers['x-client-id'] || 'anonymous';
const rateLimitResult = scenarios.rateLimit.handler(clientId);
res.set({
'X-RateLimit-Limit': rateLimitResult.limit,
'X-RateLimit-Remaining': rateLimitResult.remaining,
'X-RateLimit-Reset': rateLimitResult.reset
});
if (rateLimitResult.status === 429) {
res.set('Retry-After', rateLimitResult.retryAfter);
return res.status(429).json({
error: rateLimitResult.error,
retryAfter: rateLimitResult.retryAfter
});
}
next();
}
Using scenarios in routes/payments.js:
import express from 'express';
import { scenarios } from '../scenarios/conditionalLogic.js';
import { applyDelay } from '../middleware/delay.js';
const router = express.Router();
// POST /api/payments
router.post('/payments', applyDelay, (req, res) => {
const { cardNumber, amount, currency = 'USD' } = req.body;
// Validation
if (!cardNumber || !amount) {
return res.status(400).json({
error: 'Missing required fields',
required: ['cardNumber', 'amount']
});
}
// Apply payment scenario logic
const result = scenarios.payment.handler(cardNumber, amount);
const response = {
...result,
amount,
currency,
timestamp: new Date().toISOString()
};
// Set status code based on result
const statusCode = result.status === 'success' ? 200 :
result.status === 'pending' ? 202 :
result.status === 'declined' ? 402 : 400;
res.status(statusCode).json(response);
});
// POST /api/auth/login
router.post('/auth/login', applyDelay, (req, res) => {
const { email, password } = req.body;
const result = scenarios.auth.handler(email, password);
res.status(result.status).json(result);
});
export default router;
Configuration Options
Command-Line Flags
/create-mock-server [OPTIONS]
OPTIONS:
--spec <file> OpenAPI specification file (YAML/JSON)
Required. Supports OpenAPI 3.0+ and Swagger 2.0
--output <directory> Output directory for generated server
Default: ./mock-server
--port <number> Server port
Default: 3000
--delay <range> Response delay range in milliseconds
Format: "min-max" (e.g., "100-300")
Default: "50-150"
--stateful Enable stateful mode with in-memory persistence
Default: false (stateless)
--seed <number> Number of records to seed per collection (stateful mode)
Default: 20
--scenarios Enable conditional response scenarios
Default: false
--error-rate <percent> Error injection rate (0-100)
Default: 0 (no errors)
--cors Enable CORS with permissive settings
Default: true
--log-level <level> Logging verbosity: error, warn, info, debug
Default: info
--hot-reload Enable hot-reload with nodemon
Default: true for dev mode
EXAMPLES:
# Basic stateless mock
/create-mock-server --spec api.yaml --port 3000
# Stateful with 100 seed records
/create-mock-server --spec api.yaml --stateful --seed 100
# With error injection and scenarios
/create-mock-server --spec api.yaml --scenarios --error-rate 10 --delay 200-500
# Production-like config
/create-mock-server --spec api.yaml --delay 50-200 --log-level warn --no-hot-reload
Environment Variables
Create .env file in generated project:
# Server configuration
PORT=3000
NODE_ENV=development
LOG_LEVEL=info
# Mock behavior
STATEFUL_MODE=true
ERROR_RATE=0.05
MIN_DELAY=100
MAX_DELAY=300
# CORS settings
CORS_ORIGIN=*
CORS_CREDENTIALS=false
# Data seeding
SEED_USERS=50
SEED_PRODUCTS=100
SEED_ORDERS=200
# Feature flags
ENABLE_SCENARIOS=true
ENABLE_RATE_LIMITING=true
RATE_LIMIT_REQUESTS=100
RATE_LIMIT_WINDOW=60000
# Authentication (for protected mock endpoints)
API_KEY=mock-api-key-12345
REQUIRE_AUTH=false
Error Handling
Common Errors and Solutions
1. OpenAPI Spec Validation Failed
Error: Invalid OpenAPI specification
- Missing required field: info.version
- Invalid path parameter syntax
Solution:
# Validate spec first
npx swagger-cli validate openapi.yaml
# Check for common issues:
# - Missing required fields (info, paths)
# - Invalid references ($ref)
# - Incorrect schema syntax
2. Port Already in Use
Error: listen EADDRINUSE: address already in use :::3000
Solution:
# Find process using port
lsof -i :3000
# Kill process or use different port
/create-mock-server --spec api.yaml --port 3001
3. Faker.js Schema Mapping Failed
Warning: Could not map schema type 'customType' to faker method
Solution:
- Add custom mapping in generated
config/faker-mappings.js - Or use default faker method as fallback
// Custom mapping example
const customMappings = {
'ipAddress': () => faker.internet.ip(),
'macAddress': () => faker.internet.mac(),
'customType': () => faker.string.alphanumeric(10)
};
4. Stateful Store Memory Limit
Error: JavaScript heap out of memory
Solution:
# Increase Node.js heap size
NODE_OPTIONS="--max-old-space-size=4096" npm start
# Or reduce seed count
/create-mock-server --spec api.yaml --stateful --seed 50
5. CORS Issues in Browser
Access to fetch at 'http://localhost:3000' has been blocked by CORS policy
Solution:
// Generated CORS config already permissive, but can customize:
// middleware/cors.js
export const corsOptions = {
origin: process.env.CORS_ORIGIN || '*',
credentials: process.env.CORS_CREDENTIALS === 'true',
methods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'OPTIONS'],
allowedHeaders: ['Content-Type', 'Authorization', 'X-API-Key']
};
Best Practices
DO:
- Always validate OpenAPI spec before generating mock server
- Use stateful mode for testing complex CRUD workflows
- Set realistic delay ranges (100-300ms) to catch race conditions
- Enable error injection (5-10%) to test error handling
- Seed sufficient data (50+ records) for pagination testing
- Use scenario-based responses for authentication and payments
- Add request logging for debugging
- Version your OpenAPI spec alongside mock server
- Document test scenarios in README
- Use Docker for consistent mock server deployment
DON'T:
- Don't rely on mocks for performance testing (no real database)
- Don't commit generated node_modules to version control
- Don't expose mock servers to public internet (development only)
- Don't use real production data in mock servers
- Don't skip response schema validation
- Don't hardcode test data (use faker.js for variety)
- Don't forget to update mocks when API spec changes
- Don't use mocks as production API substitutes
TIPS:
- Use
--seedgenerously for realistic datasets - Combine
--delaywith--error-ratefor chaos testing - Create separate mock configs for different test scenarios
- Use Docker Compose for multi-service mock environments
- Add health check endpoint for orchestration tools
- Export Postman collection from generated server for QA
- Use Git tags to version mock server with API spec versions
Related Commands
/validate-openapi- Validate OpenAPI specification before mocking/test-api-endpoints- Test generated mock endpoints automatically/generate-api-client- Generate SDK clients from same OpenAPI spec/api-load-test- Load test mock server to verify performance/create-api-docs- Generate interactive documentation from OpenAPI spec/api-contract-test- Validate mock responses match OpenAPI contract
Performance Considerations
Mock Server Performance
- Stateless Mode: Handles 1000+ req/sec on modest hardware
- Stateful Mode: 500-800 req/sec with in-memory store (10k records)
- With Delay Simulation: Throughput = 1000 / avg_delay_ms requests/sec
- Memory Usage: ~50MB base, +5MB per 10k seeded records
Optimization Tips
- Use stateless mode for high-concurrency tests
- Reduce seed count if memory is constrained
- Disable logging in production mode (
LOG_LEVEL=error) - Use Redis for stateful storage beyond 100k records
- Enable clustering for multi-core utilization:
// server.js with clustering
import cluster from 'cluster';
import os from 'os';
if (cluster.isPrimary) {
const numCPUs = os.cpus().length;
for (let i = 0; i < numCPUs; i++) {
cluster.fork();
}
} else {
// Start Express server
app.listen(PORT);
}
Monitoring Mock Performance
# Request latency
curl -w "@curl-format.txt" http://localhost:3000/api/users
# Concurrent requests
ab -n 1000 -c 10 http://localhost:3000/api/users
# Memory usage
NODE_ENV=production node --inspect server.js
# Visit chrome://inspect for profiling
Security Notes for Mock Data
Important Security Warnings
- Never use production data - Mocks should use synthetic data only
- No real credentials - All passwords, API keys, tokens should be fake
- No PII leakage - Use faker.js to generate realistic but fake personal data
- Disable in production - Mock servers are development tools only
- Network isolation - Bind to localhost or use Docker networks
Secure Mock Configuration
// config/security.js
export const securityConfig = {
// Bind to localhost only (not 0.0.0.0)
host: process.env.HOST || '127.0.0.1',
// Disable in production
enabled: process.env.NODE_ENV !== 'production',
// Optional API key for mock server access
apiKey: process.env.MOCK_API_KEY,
// Rate limiting
rateLimit: {
windowMs: 60000,
max: 100
}
};
// Middleware
if (securityConfig.apiKey) {
app.use((req, res, next) => {
const providedKey = req.headers['x-api-key'];
if (providedKey !== securityConfig.apiKey) {
return res.status(401).json({ error: 'Invalid API key' });
}
next();
});
}
Data Sanitization
// Ensure no real data leaks
import { faker } from '@faker-js/faker';
faker.seed(12345); // Reproducible fake data
// Sanitize any user input
function sanitizeForMock(data) {
return {
...data,
email: faker.internet.email(),
phone: faker.phone.number(),
ssn: 'XXX-XX-' + faker.string.numeric(4),
creditCard: '****-****-****-' + faker.string.numeric(4)
};
}
Troubleshooting Guide
Issue: Mock server not starting
Symptoms: Port binding errors, module not found Diagnosis:
# Check port availability
netstat -an | grep 3000
# Check Node version
node --version # Should be 18+
# Check dependencies
npm list
Resolution:
- Kill process on port:
kill -9 $(lsof -t -i:3000) - Reinstall dependencies:
rm -rf node_modules && npm install - Use different port:
PORT=3001 npm start
Issue: OpenAPI spec not generating routes
Symptoms: No routes registered, empty routes folder Diagnosis:
# Validate OpenAPI spec
npx swagger-cli validate openapi.yaml
# Check for required fields
jq '.paths' openapi.yaml
Resolution:
- Ensure
pathssection exists with at least one endpoint - Check
$refreferences are valid - Verify schema components are defined
Issue: Faker.js generating same data
Symptoms: Identical data on each request Diagnosis: Seed is set globally Resolution:
// Remove global seed or set per-request
// Bad:
faker.seed(12345); // At module level
// Good:
function generateUser() {
faker.seed(Date.now() + Math.random()); // Unique seed
return { ... };
}
Issue: Stateful store losing data
Symptoms: Data disappears on server restart Diagnosis: In-memory store is ephemeral Resolution:
// Add persistence to disk
import fs from 'fs';
class PersistentStore extends MockStore {
constructor(filename = 'mock-data.json') {
super();
this.filename = filename;
this.load();
}
save() {
const data = {
users: Array.from(this.collections.users.entries()),
products: Array.from(this.collections.products.entries())
};
fs.writeFileSync(this.filename, JSON.stringify(data, null, 2));
}
load() {
if (fs.existsSync(this.filename)) {
const data = JSON.parse(fs.readFileSync(this.filename, 'utf8'));
this.collections.users = new Map(data.users);
this.collections.products = new Map(data.products);
}
}
}
// Save on changes
router.post('/users', (req, res) => {
const user = store.create('users', req.body);
store.save(); // Persist to disk
res.json(user);
});
Issue: CORS errors in production
Symptoms: Browser blocks requests Diagnosis: CORS origin mismatch Resolution:
// Whitelist specific origins
const corsOptions = {
origin: (origin, callback) => {
const whitelist = [
'http://localhost:3000',
'https://app.example.com'
];
if (!origin || whitelist.includes(origin)) {
callback(null, true);
} else {
callback(new Error('Not allowed by CORS'));
}
}
};
app.use(cors(corsOptions));
Version History
v1.2.0 (Current)
- Added scenario-based conditional responses
- Implemented rate limiting simulation
- Enhanced error injection with configurable rates
- Added persistent storage option for stateful mode
- Improved OpenAPI 3.1 support
v1.1.0
- Added stateful mode with in-memory CRUD operations
- Implemented data seeding with faker.js
- Added pagination and filtering support
- Enhanced delay simulation with configurable ranges
v1.0.0
- Initial release with OpenAPI 3.0 support
- Basic stateless mock generation
- Faker.js integration for realistic data
- Express.js server generation
- CORS and logging middleware
Planned Features (v1.3.0)
- WebSocket mock support
- GraphQL mock generation
- Redis-backed stateful storage
- Request/response recording for replay
- Docker Compose generation
- Postman collection export
- OpenAPI 3.1 webhooks support