A lightweight TypeScript library for mocking API endpoints during frontend development. Perfect for developing frontend applications before the backend is ready, or for testing different API scenarios.
- Easy Integration: Simple API that works with any HTTP client (fetch, axios, etc.)
- TypeScript Support: Full TypeScript support with type definitions
- Flexible Matching: Support for exact paths and dynamic parameters (
:id
) - Advanced Matching: Match requests by query parameters, headers, or custom logic
- Dynamic Responses: Create responses with custom logic
- Stateful Mocks: Built-in state management for realistic CRUD operations
- Network Error Simulation: Simulate timeouts, connection errors, and aborts
- Priority System: Control which mock takes precedence with priority levels
- Delay Simulation: Simulate network delays for realistic testing
- Request Logging: Optional request logging for debugging
- Easy Switching: Simple enable/disable for switching to real APIs
- No Dependencies: Zero runtime dependencies
- Node.js 18 or later for local development and testing (earlier versions must polyfill the Fetch API).
- Browsers or runtimes that expose
fetch
,Request
, andResponse
globals. When targeting older environments, install a fetch polyfill before creating anApiMocker
instance.
npm install @onamfc/api-mocker
Create a dedicated file for your API mocks. This keeps your mock configuration organized and separate from your main application code.
Create src/mocks/api-mocks.ts
import { createMocker } from '@onamfc/api-mocker';
// Create the mocker instance
export const apiMocker = createMocker({
baseUrl: 'https://api.yourapp.com', // Your API base URL
logRequests: true, // Enable logging to see which requests are being mocked
globalDelay: 500 // Add 500ms delay to all requests for realism
});
// Define your mock endpoints
export function setupMocks() {
// Simple GET endpoint
apiMocker.get('/users', [
{ id: 1, name: 'John Doe', email: 'john@example.com', role: 'admin' },
{ id: 2, name: 'Jane Smith', email: 'jane@example.com', role: 'user' },
{ id: 3, name: 'Bob Johnson', email: 'bob@example.com', role: 'user' }
]);
// Dynamic endpoint with parameters
apiMocker.get('/users/:id', {
id: 1,
name: 'John Doe',
email: 'john@example.com',
role: 'admin',
createdAt: '2023-01-15T10:30:00Z',
lastLogin: '2024-01-20T14:22:00Z'
});
// POST endpoint with custom status
apiMocker.post('/users', {
id: 4,
name: 'New User',
email: 'newuser@example.com',
role: 'user',
createdAt: new Date().toISOString()
}, {
status: 201,
delay: 1000 // Longer delay for POST requests
});
// PUT endpoint
apiMocker.put('/users/:id', {
id: 1,
name: 'Updated User',
email: 'updated@example.com',
role: 'admin',
updatedAt: new Date().toISOString()
});
// DELETE endpoint
apiMocker.delete('/users/:id', {
success: true,
message: 'User deleted successfully'
});
}
For React Applications:
đź’ˇ Environment variable tips: Create React App (and other Webpack-based builds) exposes values through
process.env
, while Vite exposes them viaimport.meta.env
. Use the snippet that matches your toolchain so the browser build never tries to access an undefinedprocess
object.
Create src/mocks/index.ts
For Create React App / Webpack projects:
import { setupMocks, apiMocker } from './api-mocks';
const env = typeof process !== 'undefined' && process?.env ? process.env : {};
// Only enable mocks in development
const isDevelopment = env.NODE_ENV === 'development';
const useMocks = env.REACT_APP_USE_MOCKS === 'true' || isDevelopment;
if (useMocks) {
console.log('API Mocking enabled');
setupMocks();
} else {
console.log('Using real API');
apiMocker.disable();
}
export { apiMocker };
Or for Vite-powered React projects:
import { setupMocks, apiMocker } from './api-mocks';
const env = import.meta.env;
// Only enable mocks in development
const isDevelopment = env.MODE === 'development' || env.DEV;
const useMocks = env.VITE_USE_MOCKS === 'true' || isDevelopment;
if (useMocks) {
console.log('API Mocking enabled');
setupMocks();
} else {
console.log('Using real API');
apiMocker.disable();
}
export { apiMocker };
Then in your src/index.js
or src/main.tsx
:
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
// Import mocks BEFORE your app components
import './mocks';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<App />);
For Vue Applications:
Create src/mocks/index.ts
:
import { setupMocks, apiMocker } from './api-mocks';
export function initializeMocks() {
const isDevelopment = import.meta.env.DEV;
const useMocks = import.meta.env.VITE_USE_MOCKS === 'true' || isDevelopment;
if (useMocks) {
console.log('API Mocking enabled');
setupMocks();
} else {
console.log('Using real API');
apiMocker.disable();
}
}
Then in your src/main.js
:
import { createApp } from 'vue';
import App from './App.vue';
import { initializeMocks } from './mocks';
// Initialize mocks before creating the app
initializeMocks();
createApp(App).mount('#app');
Now your existing API calls will automatically use the mocked responses:
// This will use your mocked data automatically
async function fetchUsers() {
try {
const response = await fetch('https://api.yourapp.com/users');
const users = await response.json();
console.log('Users:', users); // Will log your mocked users
return users;
} catch (error) {
console.error('Failed to fetch users:', error);
}
}
// This will also work with dynamic parameters
async function fetchUser(id) {
const response = await fetch(`https://api.yourapp.com/users/${id}`);
return response.json();
}
// POST requests work too
async function createUser(userData) {
const response = await fetch('https://api.yourapp.com/users', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(userData)
});
return response.json();
}
Factory helper that returns a new ApiMocker
instance. Use this in your application bootstrap so you can easily share a configured mocker across modules.
The main class responsible for intercepting fetch
calls.
mock(endpoint)
– Register a static endpoint. Theresponse
can be plain data, aMockResponse
object ({ data, status?, headers? }
), a nativeResponse
, or a function returning any of those (sync or async).mockDynamic(endpoint)
– Register a dynamic endpoint whosehandler
receives aRequestContext
with params, query, headers, parsed body, and shared state.get
,post
,put
,patch
,delete
– Convenience wrappers aroundmock
for common HTTP verbs.enable()
/disable()
– Toggle interception without removing registered mocks.clear()
/remove(method, path)
– Remove all mocks or a specific endpoint.setState(key, value)
/getState()
/clearState()
– Manage shared in-memory state used by dynamic handlers.updateConfig(partialConfig)
– Merge configuration updates at runtime (e.g., changebaseUrl
, add headers, adjust logging).restore()
– Restore the originalfetch
implementation. Call this when you no longer need mocking (for example in test teardown).
status
,delay
,headers
,priority
– Control the returned HTTP status, simulated latency, custom headers, and selection priority between overlapping mocks.queryParams
,requestHeaders
,match(request)
– Advanced matching rules to scope mocks to specific query strings, request headers, or arbitrary logic.networkError
– Simulate network failures ('timeout'
,'connection_refused'
, or'abort'
).MockResponse
support lets you fine-tunestatus
andheaders
on a per-response basis without mutating the endpoint definition.
// Create a more realistic user endpoint that responds based on the ID
apiMocker.mockDynamic({
path: '/users/:id',
method: 'GET',
handler: (context) => {
const userId = parseInt(context.params.id);
// Simulate user not found
if (userId > 100) {
throw new Error('User not found');
}
return {
id: userId,
name: `User ${userId}`,
email: `user${userId}@example.com`,
role: userId === 1 ? 'admin' : 'user',
avatar: `https://api.dicebear.com/7.x/avataaars/svg?seed=${userId}`,
createdAt: new Date(2023, 0, userId).toISOString(),
isOnline: Math.random() > 0.5
};
}
});
Create src/mocks/stateful-mocks.ts
:
import { apiMocker } from './api-mocks';
let nextId = 4;
// Initialize some data
apiMocker.setState('users', [
{ id: 1, name: 'John Doe', email: 'john@example.com' },
{ id: 2, name: 'Jane Smith', email: 'jane@example.com' },
{ id: 3, name: 'Bob Johnson', email: 'bob@example.com' }
]);
export function setupStatefulMocks() {
// GET all users - returns current state
apiMocker.mockDynamic({
path: '/users',
method: 'GET',
handler: (context) => {
return context.state.users || [];
}
});
// GET single user
apiMocker.mockDynamic({
path: '/users/:id',
method: 'GET',
handler: (context) => {
const users = context.state.users || [];
const user = users.find(u => u.id === parseInt(context.params.id));
if (!user) {
return { error: 'User not found' };
}
return user;
},
status: 200
});
// POST new user - adds to state
apiMocker.mockDynamic({
path: '/users',
method: 'POST',
handler: (context) => {
const users = context.state.users || [];
const newUser = {
id: nextId++,
...context.body,
createdAt: new Date().toISOString()
};
users.push(newUser);
context.state.users = users;
return newUser;
},
status: 201
});
// PUT update user - modifies state
apiMocker.mockDynamic({
path: '/users/:id',
method: 'PUT',
handler: (context) => {
const users = context.state.users || [];
const userIndex = users.findIndex(u => u.id === parseInt(context.params.id));
if (userIndex === -1) {
return { error: 'User not found' };
}
users[userIndex] = {
...users[userIndex],
...context.body,
updatedAt: new Date().toISOString()
};
context.state.users = users;
return users[userIndex];
}
});
// DELETE user - removes from state
apiMocker.mockDynamic({
path: '/users/:id',
method: 'DELETE',
handler: (context) => {
const users = context.state.users || [];
const userIndex = users.findIndex(u => u.id === parseInt(context.params.id));
if (userIndex === -1) {
return { error: 'User not found' };
}
users.splice(userIndex, 1);
context.state.users = users;
return { success: true, message: 'User deleted' };
}
});
}
// Match based on query parameters
apiMocker.mock({
path: '/users',
method: 'GET',
response: [{ id: 1, name: 'Admin User', role: 'admin' }],
queryParams: { role: 'admin' }
});
apiMocker.mock({
path: '/users',
method: 'GET',
response: [
{ id: 2, name: 'Regular User 1', role: 'user' },
{ id: 3, name: 'Regular User 2', role: 'user' }
],
queryParams: { role: 'user' }
});
// Match based on request headers
apiMocker.mock({
path: '/api/data',
method: 'GET',
response: { format: 'json', data: [1, 2, 3] },
requestHeaders: { 'Accept': 'application/json' }
});
// Custom matching logic
apiMocker.mock({
path: '/protected',
method: 'GET',
response: { message: 'Access granted', data: 'secret' },
match: (request) => {
const authHeader = request.headers.get('Authorization');
return authHeader && authHeader.startsWith('Bearer ');
}
});
// HTTP error responses
apiMocker.get('/users/999', { error: 'User not found' }, {
status: 404,
delay: 300
});
apiMocker.post('/users', { error: 'Validation failed', details: ['Email is required'] }, {
status: 400
});
// Network error simulation
apiMocker.mock({
path: '/flaky-endpoint',
method: 'GET',
response: {},
networkError: 'timeout' // Simulates network timeout
});
// Conditional errors
apiMocker.mockDynamic({
path: '/unreliable',
method: 'GET',
handler: () => {
// 30% chance of success, 70% chance of error
if (Math.random() > 0.3) {
throw new Error('Service temporarily unavailable');
}
return { status: 'success', data: 'Hello World' };
}
});
Here's a recommended file structure for organizing your mocks:
src/
├── mocks/
│ ├── index.ts # Main mock initialization
│ ├── api-mocks.ts # Basic mock definitions
│ ├── stateful-mocks.ts # Stateful CRUD operations
│ ├── error-mocks.ts # Error scenarios
│ └── data/
│ ├── users.ts # Mock user data
│ ├── products.ts # Mock product data
│ └── orders.ts # Mock order data
├── services/
│ ├── api.ts # Your API service functions
│ └── userService.ts # User-specific API calls
└── components/
└── ...
Example src/mocks/data/users.ts
:
export const mockUsers = [
{
id: 1,
name: 'John Doe',
email: 'john@example.com',
role: 'admin',
avatar: 'https://api.dicebear.com/7.x/avataaars/svg?seed=1',
createdAt: '2023-01-15T10:30:00Z',
isActive: true
},
{
id: 2,
name: 'Jane Smith',
email: 'jane@example.com',
role: 'user',
avatar: 'https://api.dicebear.com/7.x/avataaars/svg?seed=2',
createdAt: '2023-02-20T14:22:00Z',
isActive: true
},
// ... more users
];
export const mockUserProfiles = {
1: {
bio: 'Software engineer with 10 years of experience',
location: 'San Francisco, CA',
website: 'https://johndoe.dev',
socialLinks: {
twitter: '@johndoe',
linkedin: 'john-doe-dev'
}
},
// ... more profiles
};
Create a .env
file in your project root:
# Development
REACT_APP_USE_MOCKS=true
REACT_APP_API_URL=https://api.yourapp.com
# For Vue projects, use VITE_ prefix
VITE_USE_MOCKS=true
VITE_API_URL=https://api.yourapp.com
// src/mocks/index.ts (Create React App / Webpack)
import { setupMocks, setupStatefulMocks, apiMocker } from './api-mocks';
const env = typeof process !== 'undefined' && process?.env ? process.env : {};
const config = {
// Enable mocks in development or when explicitly requested
enabled: env.NODE_ENV === 'development' || env.REACT_APP_USE_MOCKS === 'true',
// Use stateful mocks for more realistic testing
stateful: env.REACT_APP_STATEFUL_MOCKS === 'true',
// Add delays to simulate real network conditions
realistic: env.REACT_APP_REALISTIC_DELAYS === 'true'
};
if (config.enabled) {
console.log('Initializing API mocks...');
// Configure the mocker
apiMocker.updateConfig({
baseUrl: env.REACT_APP_API_URL,
logRequests: true,
globalDelay: config.realistic ? 500 : 0
});
// Setup mocks
if (config.stateful) {
setupStatefulMocks();
} else {
setupMocks();
}
console.log('API mocks ready');
} else {
console.log('Using real API endpoints');
apiMocker.disable();
}
// src/mocks/index.ts (Vite)
import { setupMocks, setupStatefulMocks, apiMocker } from './api-mocks';
const env = import.meta.env;
const config = {
// Enable mocks in development or when explicitly requested
enabled: env.DEV || env.MODE === 'development' || env.VITE_USE_MOCKS === 'true',
// Use stateful mocks for more realistic testing
stateful: env.VITE_STATEFUL_MOCKS === 'true',
// Add delays to simulate real network conditions
realistic: env.VITE_REALISTIC_DELAYS === 'true'
};
if (config.enabled) {
console.log('Initializing API mocks...');
// Configure the mocker
apiMocker.updateConfig({
baseUrl: env.VITE_API_URL,
logRequests: true,
globalDelay: config.realistic ? 500 : 0
});
// Setup mocks
if (config.stateful) {
setupStatefulMocks();
} else {
setupMocks();
}
console.log('API mocks ready');
} else {
console.log('Using real API endpoints');
apiMocker.disable();
}
When you're ready to connect to real APIs, you have several options:
// Simply disable all mocking
apiMocker.disable();
// Remove specific endpoints as real ones become available
apiMocker.remove('GET', '/users');
apiMocker.remove('POST', '/users');
// Keep other mocks active
// apiMocker.get('/products', mockProducts); // Still mocked
// Use environment variables to control which endpoints are mocked (CRA / Webpack)
const env = typeof process !== 'undefined' && process?.env ? process.env : {};
const mockConfig = {
users: env.REACT_APP_MOCK_USERS !== 'false',
products: env.REACT_APP_MOCK_PRODUCTS !== 'false',
orders: env.REACT_APP_MOCK_ORDERS !== 'false'
};
if (mockConfig.users) {
setupUserMocks();
}
if (mockConfig.products) {
setupProductMocks();
}
if (mockConfig.orders) {
setupOrderMocks();
}
// Use environment variables to control which endpoints are mocked (Vite)
const env = import.meta.env;
const mockConfig = {
users: env.VITE_MOCK_USERS !== 'false',
products: env.VITE_MOCK_PRODUCTS !== 'false',
orders: env.VITE_MOCK_ORDERS !== 'false'
};
if (mockConfig.users) {
setupUserMocks();
}
if (mockConfig.products) {
setupProductMocks();
}
if (mockConfig.orders) {
setupOrderMocks();
}
import { ApiMocker, createMocker } from '@onamfc/api-mocker';
// Using the factory function (recommended)
const mocker = createMocker({
baseUrl: 'https://api.example.com',
globalDelay: 500,
logRequests: true,
globalHeaders: {
'Content-Type': 'application/json',
'X-Custom-Header': 'value'
}
});
// Or using the class directly
const mocker = new ApiMocker(config);
interface MockConfig {
baseUrl?: string; // Base URL to match against
globalDelay?: number; // Default delay for all requests (ms)
globalHeaders?: Record<string, string>; // Default headers for all responses
logRequests?: boolean; // Enable/disable request logging
defaultPriority?: number; // Default priority for endpoints
}
// Using convenience methods
mocker
.get('/users', [{ id: 1, name: 'John' }])
.post('/users', { id: 2, name: 'Jane' })
.put('/users/:id', { id: 1, name: 'Updated John' })
.delete('/users/:id', { success: true });
// Using the generic mock method
mocker.mock({
path: '/products',
method: 'GET',
response: [{ id: 1, name: 'Product 1' }],
status: 200,
delay: 1000,
headers: { 'X-Total-Count': '1' }
});
// Enhanced dynamic response with full context
mocker.mockDynamic({
path: '/users/:id',
method: 'GET',
handler: (context) => {
const userId = context.params.id;
const includeEmail = context.queryParams.include === 'email';
const isAdmin = context.headers['x-user-role'] === 'admin';
return {
id: userId,
name: `User ${userId}`,
email: includeEmail ? `user${userId}@example.com` : undefined,
role: isAdmin ? 'admin' : 'user',
timestamp: new Date().toISOString()
};
},
delay: 500
});
// Stateful dynamic response
mocker.mockDynamic({
path: '/analytics/pageview',
method: 'POST',
handler: (context) => {
// Initialize analytics if not exists
if (!context.state.analytics) {
context.state.analytics = { pageViews: 0, uniqueVisitors: new Set() };
}
const { page, userId } = context.body;
context.state.analytics.pageViews++;
context.state.analytics.uniqueVisitors.add(userId);
return {
page,
totalViews: context.state.analytics.pageViews,
uniqueVisitors: context.state.analytics.uniqueVisitors.size
};
}
});
// Enable/disable mocking
mocker.enable();
mocker.disable();
// Clear all mocks
mocker.clear();
// Remove specific mock
mocker.remove('GET', '/users');
// State management
mocker.setState('key', 'value');
const state = mocker.getState();
mocker.clearState();
// Restore original fetch
mocker.restore();
// Update configuration
mocker.updateConfig({
logRequests: false,
globalDelay: 1000
});
- Organize Your Mocks: Keep mock definitions in separate files organized by feature or endpoint
- Use Environment Variables: Control mock behavior through environment variables
- Realistic Data: Use realistic mock data that matches your actual API responses
- Test Error Scenarios: Mock both success and error responses to test error handling
- Gradual Migration: Disable mocks endpoint by endpoint as real APIs become available
- Type Safety: Use TypeScript interfaces for your API responses
interface User {
id: number;
name: string;
email: string;
}
mocker.get('/users', [] as User[]);
- State Management: Use stateful mocks for realistic CRUD operations
- Network Conditions: Use delays and network errors to test under realistic conditions
Mocks not working:
- Check that mocks are initialized before your API calls
- Verify the
baseUrl
matches your API calls - Ensure mocking is enabled (
mocker.enable()
)
TypeScript errors:
- Make sure you have proper type definitions for your mock data
- Use
as
assertions when needed for complex types
State not persisting:
- Remember that state is only maintained within the same session
- Use
mocker.setState()
to initialize state if needed
Priority conflicts:
- Use explicit priorities when you have overlapping endpoints
- More specific paths automatically get higher priority
npm install
npm test
npm run build
These commands run the Jest test suite (using the Node 18 runtime) and build the distributable bundles under dist/
.
Issues and pull requests are welcome! Please visit the GitHub repository.