MSW Mocking
Mock Service Worker (MSW) intercepts network requests in the browser and returns mock responses. This lets you develop and test themes without a running backend or a real shop.
Activation
Start the dev server with VITE_USE_MSW=true:
VITE_USE_MSW=true VITE_SHOP_SLUG=demo bun run dev
The shop slug can be anything when using mocks — the mock handlers return the same data regardless of slug.
How It Works
When VITE_USE_MSW is true, the theme conditionally imports and starts the MSW service worker before rendering:
// main.tsx (simplified)
if (import.meta.env.VITE_USE_MSW === 'true') {
const { worker } = await import('@/mocks/browser');
await worker.start({ onUnhandledRequest: 'bypass' });
}
The onUnhandledRequest: 'bypass' setting means requests that do not match any handler (e.g. Vite HMR, static assets) pass through normally.
The worker is set up in a separate file:
// mocks/browser.ts
import { setupWorker } from 'msw/browser';
import { handlers } from './handlers';
export const worker = setupWorker(...handlers);
MSW requires a service worker file (mockServiceWorker.js) in your public/ directory. The reference theme already includes this file. If you are creating a new theme, generate it with bunx msw init public/.
Mocked Endpoints
The following API endpoints are intercepted by the mock handlers:
| Endpoint | Method | Response |
|---|
*/v1/storefront/shops/name/:storeSlug | GET | Mock shop object ({ status: 200, data: { shop: mockShop } }) |
*/v1/storefront/shops/domain/:domain | GET | Mock shop object ({ status: 200, data: { shop: mockShop } }) |
*/v1/storefront/products/public/:storeSlug | GET | Array of mock products ({ status: 200, data: { products: mockProducts } }) |
*/v1/storefront/products/unique/:id | GET | Single product by ID or slug. Returns 404 if not found. |
*/v1/storefront/coupons/check | POST | Coupon validation. Codes SAVE10 and FLORAIN return a valid 10% discount. Other codes return invalid. |
Wildcard Matching
Handlers use the '*/v1/...' URL pattern. The leading * matches any protocol and host, so mocks work regardless of your VITE_API_BASE_URL setting — whether it points to localhost, https://api.shoppex.io, or anything else.
What Is Not Mocked
The following SDK features hit the real API and are not covered by the default mock handlers:
| Feature | SDK Method | Endpoint |
|---|
| Checkout | checkout() | POST /v1/storefront/invoices/from-cart |
| Theme Settings | fetchPublishedThemeSettings() | GET /v1/storefront/themes/builder/published/:shopSlug |
| Invoices | getInvoice(), getInvoiceStatus() | GET /v1/storefront/invoices/unique/:invoiceId
GET /v1/storefront/invoices/status/:invoiceId |
| Pages | getPages(), getPage() | GET /v1/storefront/shops/name/:storeSlug/pages
GET /v1/storefront/shops/name/:storeSlug/pages/:slug |
| Navigation | getMenus(), getMenu() | GET /v1/storefront/shops/name/:storeSlug/menus
GET /v1/storefront/shops/name/:storeSlug/menus/:title |
| Analytics | trackPageView() | POST /v1/storefront/shops/:storeSlug/ping |
If you need to mock additional endpoints, add handlers to mocks/handlers.ts:
import { http, HttpResponse } from 'msw';
// Example: mock the pages endpoint
http.get('*/v1/storefront/shops/name/:storeSlug/pages', () => {
return HttpResponse.json({
status: 200,
data: {
pages: [
// Minimal example: include additional fields if your UI relies on them.
{
id: 'page_1',
slug: 'about',
name: 'About Us',
content: '<p>Hello</p>',
template_type: 'custom',
is_system: false,
settings: null,
visible_after: null,
created_at: Date.now(),
updated_at: Date.now(),
},
],
},
});
});
MSW vs Public API
| MSW Mocks | Public API |
|---|
| Requires backend | No | No (uses api.shoppex.io) |
| Data | Static mock data | Real shop data |
| Speed | Instant (no network) | Network latency |
| Coverage | 5 endpoints (see above) | All endpoints |
| Best for | UI development, prototyping | Integration testing, final QA |
| Env var | VITE_USE_MSW=true | VITE_API_BASE_URL=https://api.shoppex.io |
Next Steps