Create a mock API server for testing
Generates production-ready mock API servers from OpenAPI specs with realistic fake data, stateful CRUD operations, and configurable response scenarios.
/plugin marketplace add jeremylongshore/claude-code-plugins-plus/plugin install api-mock-server@claude-code-plugins-plusGenerate 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.
This command generates mock servers using Express.js with faker-js for realistic data generation. The implementation prioritizes:
Alternatives Considered:
USE WHEN:
DON'T USE WHEN:
Required:
Optional:
Install Dependencies:
npm install express @faker-js/faker swagger-parser cors
npm install --save-dev nodemon @types/express
Ensure your OpenAPI spec is valid and contains response schemas with examples:
# Validate OpenAPI spec
npx swagger-cli validate openapi.yaml
Command analyzes the OpenAPI spec and generates:
Customize mock server with:
Launch the generated server with hot-reload:
npm run dev # Development mode with nodemon
npm start # Production mode
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"}'
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
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`);
});
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;
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;
/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
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
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:
config/faker-mappings.js// 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']
};
--seed generously for realistic datasets--delay with --error-rate for chaos testing/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 contractLOG_LEVEL=error)// 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);
}
# 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
// 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();
});
}
// 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)
};
}
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 -9 $(lsof -t -i:3000)rm -rf node_modules && npm installPORT=3001 npm startSymptoms: 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:
paths section exists with at least one endpoint$ref references are validSymptoms: 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 { ... };
}
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);
});
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));