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, andResponseglobals. When targeting older environments, install a fetch polyfill before creating anApiMockerinstance.
npm install @onamfc/api-mockerCreate 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 instanceexportconstapiMocker=createMocker({baseUrl: 'https://api.yourapp.com',// Your API base URLlogRequests: true,// Enable logging to see which requests are being mockedglobalDelay: 500// Add 500ms delay to all requests for realism});// Define your mock endpointsexportfunctionsetupMocks(){// Simple GET endpointapiMocker.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 parametersapiMocker.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 statusapiMocker.post('/users',{id: 4,name: 'New User',email: 'newuser@example.com',role: 'user',createdAt: newDate().toISOString()},{status: 201,delay: 1000// Longer delay for POST requests});// PUT endpointapiMocker.put('/users/:id',{id: 1,name: 'Updated User',email: 'updated@example.com',role: 'admin',updatedAt: newDate().toISOString()});// DELETE endpointapiMocker.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 undefinedprocessobject.
Create src/mocks/index.ts
For Create React App / Webpack projects:
import{setupMocks,apiMocker}from'./api-mocks';constenv=typeofprocess!=='undefined'&&process?.env ? process.env : {};// Only enable mocks in developmentconstisDevelopment=env.NODE_ENV==='development';constuseMocks=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';constenv=import.meta.env;// Only enable mocks in developmentconstisDevelopment=env.MODE==='development'||env.DEV;constuseMocks=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:
importReactfrom'react';importReactDOMfrom'react-dom/client';importAppfrom'./App';// Import mocks BEFORE your app componentsimport'./mocks';constroot=ReactDOM.createRoot(document.getElementById('root'));root.render(<App/>);For Vue Applications:
Create src/mocks/index.ts:
import{setupMocks,apiMocker}from'./api-mocks';exportfunctioninitializeMocks(){constisDevelopment=import.meta.env.DEV;constuseMocks=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';importAppfrom'./App.vue';import{initializeMocks}from'./mocks';// Initialize mocks before creating the appinitializeMocks();createApp(App).mount('#app');Now your existing API calls will automatically use the mocked responses:
// This will use your mocked data automaticallyasyncfunctionfetchUsers(){try{constresponse=awaitfetch('https://api.yourapp.com/users');constusers=awaitresponse.json();console.log('Users:',users);// Will log your mocked usersreturnusers;}catch(error){console.error('Failed to fetch users:',error);}}// This will also work with dynamic parametersasyncfunctionfetchUser(id){constresponse=awaitfetch(`https://api.yourapp.com/users/${id}`);returnresponse.json();}// POST requests work tooasyncfunctioncreateUser(userData){constresponse=awaitfetch('https://api.yourapp.com/users',{method: 'POST',headers: {'Content-Type': 'application/json'},body: JSON.stringify(userData)});returnresponse.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. Theresponsecan be plain data, aMockResponseobject ({ data, status?, headers? }), a nativeResponse, or a function returning any of those (sync or async).mockDynamic(endpoint)– Register a dynamic endpoint whosehandlerreceives aRequestContextwith params, query, headers, parsed body, and shared state.get,post,put,patch,delete– Convenience wrappers aroundmockfor 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 originalfetchimplementation. 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').MockResponsesupport lets you fine-tunestatusandheaderson a per-response basis without mutating the endpoint definition.
// Create a more realistic user endpoint that responds based on the IDapiMocker.mockDynamic({path: '/users/:id',method: 'GET',handler: (context)=>{constuserId=parseInt(context.params.id);// Simulate user not foundif(userId>100){thrownewError('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: newDate(2023,0,userId).toISOString(),isOnline: Math.random()>0.5};}});Create src/mocks/stateful-mocks.ts:
import{apiMocker}from'./api-mocks';letnextId=4;// Initialize some dataapiMocker.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'}]);exportfunctionsetupStatefulMocks(){// GET all users - returns current stateapiMocker.mockDynamic({path: '/users',method: 'GET',handler: (context)=>{returncontext.state.users||[];}});// GET single userapiMocker.mockDynamic({path: '/users/:id',method: 'GET',handler: (context)=>{constusers=context.state.users||[];constuser=users.find(u=>u.id===parseInt(context.params.id));if(!user){return{error: 'User not found'};}returnuser;},status: 200});// POST new user - adds to stateapiMocker.mockDynamic({path: '/users',method: 'POST',handler: (context)=>{constusers=context.state.users||[];constnewUser={id: nextId++,
...context.body,createdAt: newDate().toISOString()};users.push(newUser);context.state.users=users;returnnewUser;},status: 201});// PUT update user - modifies stateapiMocker.mockDynamic({path: '/users/:id',method: 'PUT',handler: (context)=>{constusers=context.state.users||[];constuserIndex=users.findIndex(u=>u.id===parseInt(context.params.id));if(userIndex===-1){return{error: 'User not found'};}users[userIndex]={
...users[userIndex],
...context.body,updatedAt: newDate().toISOString()};context.state.users=users;returnusers[userIndex];}});// DELETE user - removes from stateapiMocker.mockDynamic({path: '/users/:id',method: 'DELETE',handler: (context)=>{constusers=context.state.users||[];constuserIndex=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 parametersapiMocker.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 headersapiMocker.mock({path: '/api/data',method: 'GET',response: {format: 'json',data: [1,2,3]},requestHeaders: {'Accept': 'application/json'}});// Custom matching logicapiMocker.mock({path: '/protected',method: 'GET',response: {message: 'Access granted',data: 'secret'},match: (request)=>{constauthHeader=request.headers.get('Authorization');returnauthHeader&&authHeader.startsWith('Bearer ');}});// HTTP error responsesapiMocker.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 simulationapiMocker.mock({path: '/flaky-endpoint',method: 'GET',response: {},networkError: 'timeout'// Simulates network timeout});// Conditional errorsapiMocker.mockDynamic({path: '/unreliable',method: 'GET',handler: ()=>{// 30% chance of success, 70% chance of errorif(Math.random()>0.3){thrownewError('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:
exportconstmockUsers=[{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];exportconstmockUserProfiles={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';constenv=typeofprocess!=='undefined'&&process?.env ? process.env : {};constconfig={// Enable mocks in development or when explicitly requestedenabled: env.NODE_ENV==='development'||env.REACT_APP_USE_MOCKS==='true',// Use stateful mocks for more realistic testingstateful: env.REACT_APP_STATEFUL_MOCKS==='true',// Add delays to simulate real network conditionsrealistic: env.REACT_APP_REALISTIC_DELAYS==='true'};if(config.enabled){console.log('Initializing API mocks...');// Configure the mockerapiMocker.updateConfig({baseUrl: env.REACT_APP_API_URL,logRequests: true,globalDelay: config.realistic ? 500 : 0});// Setup mocksif(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';constenv=import.meta.env;constconfig={// Enable mocks in development or when explicitly requestedenabled: env.DEV||env.MODE==='development'||env.VITE_USE_MOCKS==='true',// Use stateful mocks for more realistic testingstateful: env.VITE_STATEFUL_MOCKS==='true',// Add delays to simulate real network conditionsrealistic: env.VITE_REALISTIC_DELAYS==='true'};if(config.enabled){console.log('Initializing API mocks...');// Configure the mockerapiMocker.updateConfig({baseUrl: env.VITE_API_URL,logRequests: true,globalDelay: config.realistic ? 500 : 0});// Setup mocksif(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 mockingapiMocker.disable();// Remove specific endpoints as real ones become availableapiMocker.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)constenv=typeofprocess!=='undefined'&&process?.env ? process.env : {};constmockConfig={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)constenv=import.meta.env;constmockConfig={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)constmocker=createMocker({baseUrl: 'https://api.example.com',globalDelay: 500,logRequests: true,globalHeaders: {'Content-Type': 'application/json','X-Custom-Header': 'value'}});// Or using the class directlyconstmocker=newApiMocker(config);interfaceMockConfig{baseUrl?: string;// Base URL to match againstglobalDelay?: number;// Default delay for all requests (ms)globalHeaders?: Record<string,string>;// Default headers for all responseslogRequests?: boolean;// Enable/disable request loggingdefaultPriority?: number;// Default priority for endpoints}// Using convenience methodsmocker.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 methodmocker.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 contextmocker.mockDynamic({path: '/users/:id',method: 'GET',handler: (context)=>{constuserId=context.params.id;constincludeEmail=context.queryParams.include==='email';constisAdmin=context.headers['x-user-role']==='admin';return{id: userId,name: `User ${userId}`,email: includeEmail ? `user${userId}@example.com` : undefined,role: isAdmin ? 'admin' : 'user',timestamp: newDate().toISOString()};},delay: 500});// Stateful dynamic responsemocker.mockDynamic({path: '/analytics/pageview',method: 'POST',handler: (context)=>{// Initialize analytics if not existsif(!context.state.analytics){context.state.analytics={pageViews: 0,uniqueVisitors: newSet()};}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 mockingmocker.enable();mocker.disable();// Clear all mocksmocker.clear();// Remove specific mockmocker.remove('GET','/users');// State managementmocker.setState('key','value');conststate=mocker.getState();mocker.clearState();// Restore original fetchmocker.restore();// Update configurationmocker.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
interfaceUser{id: number;name: string;email: string;}mocker.get('/users',[]asUser[]);- 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
baseUrlmatches your API calls - Ensure mocking is enabled (
mocker.enable())
TypeScript errors:
- Make sure you have proper type definitions for your mock data
- Use
asassertions 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 buildThese 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.