Platform
    • RTK Query
    • Mock Server (MSW)
    • Data Converters
    • OpenAPI Codegen
Projects
  • API Connections & Tools
  1. Documentation
  2. Mock Server (MSW)

Mock Server (MSW)

A tool for intercepting network requests at the browser level. Allows frontend development independent of backend readiness using typed mocks.

API Mocking (MSW Documentation)

Benefits: Allows testing error scenarios, network delays, and various data states without a real backend.

Mock Layer Structure

Naming and Location Rules

  • Handlers:
    Located in src/entities/[entity-name]/handlers/. Files named [entity-name].handlers.ts. Contain logic for intercepting entity-specific requests.
  • Mocks:
    Located in src/entities/[entity-name]/mock/. Files named [entity-name].mock.ts. Contain static or dynamic data simulating a server response.
  • Configuration:
    General MSW settings are in src/shared/api/msw/. All handlers are registered in handlers.ts.

011. Creating Mocks (backend DTO seed)

Define seed data in backend DTO shape. Handlers must not return frontend models if RTK converters run in transformResponse.
entities/[entity-name]/mock/[entity-name].mock.ts
1import type { TEntityBackend } from "../types"; 2 3/** Seed in backend DTO shape — never frontend models */ 4export const ENTITY_MOCK: TEntityBackend[] = [ 5 { 6 id: "1", 7 full_name: "John Doe", 8 created_at: "2024-03-22T10:00:00Z", 9 status_code: "active" 10 }, 11 { 12 id: "2", 13 full_name: "Jane Smith", 14 created_at: "2024-03-21T10:00:00Z", 15 status_code: "pending" 16 } 17];

01b1b. Optional Mock Store

Mutable store with reset for mutations and Vitest isolation. Skip for read-only static lists.
entities/[entity-name]/mock/[entity-name].store.ts
1import type { TEntityBackend, TEntitiesBackend } from "../types"; 2import { ENTITY_MOCK } from "./entity.mock"; 3 4const createStore = () => 5 new Map(ENTITY_MOCK.map((item) => [item.id, structuredClone(item)])); 6 7let store = createStore(); 8 9/** Call from Vitest beforeEach for isolation — optional for read-only mocks */ 10export const resetEntityStore = () => { 11 store = createStore(); 12}; 13 14export const listEntitiesBackend = (): TEntitiesBackend => ({ 15 items: [...store.values()], 16 total_count: store.size 17});

022. createMockHandler Utils

Shared factory using VITE_BASE_URL, delay (0 in tests), JSON body parse, and validation error helpers.
shared/api/msw/utils.ts
1import { HttpResponse, delay, http } from "msw"; 2import { ENV } from "@/shared/config"; 3 4const BASE_URL = (ENV.VITE_BASE_URL || "").replace(/\/$/, ""); 5const MOCK_RESPONSE_DELAY_MS = import.meta.env.MODE === "test" ? 0 : 500; 6 7export const createMockHandler = (endpoint, resolver) => 8 http[endpoint.method.toLowerCase()]( 9 `${BASE_URL}${endpoint.url}`, 10 async ({ request, params }) => { 11 await delay(MOCK_RESPONSE_DELAY_MS); 12 let body; 13 try { 14 body = await request.clone().json(); 15 } catch { 16 /* no body */ 17 } 18 return resolver({ 19 body, 20 query: new URL(request.url).searchParams, 21 params, 22 request 23 }); 24 } 25 ); 26 27export const mockValidationError = (loc, msg) => 28 HttpResponse.json( 29 { detail: [{ loc, msg, type: "value_error" }] }, 30 { status: 422 } 31 );

023. Defining Handlers

Use createMockHandler and return backend envelopes / error contracts matching the real API.
entities/[entity-name]/handlers/[entity-name].handlers.ts
1import { HttpResponse } from "msw"; 2import { 3 createMockHandler, 4 mockValidationError 5} from "@/shared/api/msw/utils"; 6import { listEntitiesBackend } from "../mock"; 7 8/** 9 * Return backend DTO. RTK transformResponse + converters map to 10 * IPaginationResponse / domain models — do not return { data, total } here. 11 */ 12export const entityHandlers = [ 13 createMockHandler( 14 { url: "/entities", method: "GET", _types: { body: undefined } }, 15 ({ query }) => { 16 const page = Number(query.get("page") ?? 1); 17 if (!Number.isInteger(page) || page < 1) { 18 return mockValidationError(["query", "page"], "Invalid page"); 19 } 20 return HttpResponse.json(listEntitiesBackend()); 21 } 22 ) 23];

034. Browser Worker

Create a worker instance in shared/api/msw/browser.ts to intercept browser requests.
shared/api/msw/browser.ts
1import { setupWorker } from "msw/browser"; 2import { handlers } from "./handlers"; 3 4export const worker = setupWorker(...handlers);

03b4b. Node Server (Vitest)

Create setupServer in shared/api/msw/server.ts for unit/integration tests.
shared/api/msw/server.ts
1import { setupServer } from "msw/node"; 2import { handlers } from "./handlers"; 3 4/** Vitest / Node tests */ 5export const server = setupServer(...handlers);

045. Handlers Registry

Assemble handlers from all entities into a single array in shared/api/msw/handlers.ts.
shared/api/msw/handlers.ts
1import { entityOneHandlers } from "@/entities/entity-one/handlers"; 2import { entityTwoHandlers } from "@/entities/entity-two/handlers"; 3 4export const handlers = [ 5 ...entityOneHandlers, 6 ...entityTwoHandlers 7];

056. App Initialization

Create an async startup function in app/init/msw.ts that checks environment flags.
app/init/msw.ts
1import { ENV } from "@/shared/config"; 2 3export const initMsw = async () => { 4 if (import.meta.env.DEV && ENV.VITE_ENABLE_MSW === "true") { 5 const { worker } = await import("@/shared/api/msw/browser"); 6 return worker.start({ 7 onUnhandledRequest: "bypass" 8 }); 9 } 10 return Promise.resolve(); 11};

067. Entry Point (Main)

Wait for MSW to start before rendering the application in src/main.tsx.
main.tsx
1import { StrictMode } from "react"; 2import { createRoot } from "react-dom/client"; 3import { initMsw } from "./app/init/msw"; 4import App from "./app"; 5 6const container = document.getElementById("root"); 7 8if (container) { 9 const root = createRoot(container); 10 11 initMsw().then(() => { 12 root.render( 13 <StrictMode> 14 <App /> 15 </StrictMode> 16 ); 17 }); 18}

078. Environment Variables

Managing MSW activation via the .env file (VITE_ENABLE_MSW, VITE_BASE_URL).
.env
1VITE_ENABLE_MSW=true