Files
gh-jeremylongshore-claude-c…/commands/create-mock-server.md
2025-11-29 18:52:26 +08:00

1395 lines
36 KiB
Markdown

---
description: Create a mock API server for testing
shortcut: 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:
1. **OpenAPI-First Approach**: Generates endpoints directly from OpenAPI/Swagger specs to ensure contract compliance
2. **Stateful Mode**: Optional in-memory persistence allows testing CRUD workflows with realistic state management
3. **Scenario-Based Responses**: Conditional logic based on request parameters enables testing edge cases
4. **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:**
```bash
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:
```bash
# 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:
```bash
npm run dev # Development mode with nodemon
npm start # Production mode
```
### Step 5: Test and Iterate
Verify mock endpoints match OpenAPI contract:
```bash
# 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):**
```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:**
```bash
# Generate mock server from OpenAPI spec
/create-mock-server \
--spec openapi.yaml \
--output ./mock-api \
--port 3000 \
--delay 100-300
```
**Generated routes/users.js:**
```javascript
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:**
```javascript
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:**
```bash
# 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):**
```javascript
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:**
```javascript
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:**
```javascript
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:**
```bash
# 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:**
```javascript
/**
* 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:**
```javascript
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
```bash
/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:
```bash
# 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:**
```bash
# 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:**
```bash
# 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
```javascript
// 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:**
```bash
# 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:**
```javascript
// 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:
1. Always validate OpenAPI spec before generating mock server
2. Use stateful mode for testing complex CRUD workflows
3. Set realistic delay ranges (100-300ms) to catch race conditions
4. Enable error injection (5-10%) to test error handling
5. Seed sufficient data (50+ records) for pagination testing
6. Use scenario-based responses for authentication and payments
7. Add request logging for debugging
8. Version your OpenAPI spec alongside mock server
9. Document test scenarios in README
10. Use Docker for consistent mock server deployment
### DON'T:
1. Don't rely on mocks for performance testing (no real database)
2. Don't commit generated node_modules to version control
3. Don't expose mock servers to public internet (development only)
4. Don't use real production data in mock servers
5. Don't skip response schema validation
6. Don't hardcode test data (use faker.js for variety)
7. Don't forget to update mocks when API spec changes
8. Don't use mocks as production API substitutes
### TIPS:
- Use `--seed` generously for realistic datasets
- Combine `--delay` with `--error-rate` for 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
1. Use stateless mode for high-concurrency tests
2. Reduce seed count if memory is constrained
3. Disable logging in production mode (`LOG_LEVEL=error`)
4. Use Redis for stateful storage beyond 100k records
5. Enable clustering for multi-core utilization:
```javascript
// 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
```bash
# 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
1. **Never use production data** - Mocks should use synthetic data only
2. **No real credentials** - All passwords, API keys, tokens should be fake
3. **No PII leakage** - Use faker.js to generate realistic but fake personal data
4. **Disable in production** - Mock servers are development tools only
5. **Network isolation** - Bind to localhost or use Docker networks
### Secure Mock Configuration
```javascript
// 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
```javascript
// 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:**
```bash
# 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:**
```bash
# Validate OpenAPI spec
npx swagger-cli validate openapi.yaml
# Check for required fields
jq '.paths' openapi.yaml
```
**Resolution:**
- Ensure `paths` section exists with at least one endpoint
- Check `$ref` references 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:**
```javascript
// 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:**
```javascript
// 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:**
```javascript
// 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