This example demonstrates a Frontend-First development workflow using ObjectStack.
It runs the entire ObjectStack Runtime (Kernel) directly in the browser using a Service Worker. This allows you to develop fully functional React applications with CRUD capabilities, validation, and API interactions without running a backend server.
Instead of mocking individual HTTP endpoints manually, this project spins up a real ObjectStack instance inside the browser memory.
graph TD
Client["React App <br/> @objectstack/client"] -->|REST API Calls| Network["Browser Network Layer"]
Network -->|Intercepted by| SW["Service Worker <br/> MockServiceWorker"]
SW -->|Delegates to| Kernel["ObjectStack Kernel <br/> (Running in Browser)"]
Kernel -->|Uses| MemoryDriver["In-Memory Driver"]
Kernel -.->|Reads| Config["objectstack.config.ts <br/> Schema Definitions"]
- Zero-Backend Development: Develop the entire frontend flow before the backend exists.
- Real Logic: It's not just static JSON. The Kernel enforces schema validation, defaults, and even automation logic.
- Shared Schema: The same
objectstack.config.tsused here can be deployed to the real Node.js server later. - Instant Feedback: Changes to the schema are reflected immediately in the browser.
Here is how to implement this architecture in your own project.
Create an objectstack.config.ts to define your data models and application structure.
// objectstack.config.ts
import { defineStack } from '@objectstack/spec';
import { ObjectSchema, Field } from '@objectstack/spec/data';
import { App } from '@objectstack/spec/ui';
export const TaskObject = ObjectSchema.create({
name: 'task',
label: 'Task',
fields: {
subject: Field.text({ label: 'Subject', required: true }),
priority: Field.number({ label: 'Priority', defaultValue: 5 }),
is_completed: Field.boolean({ label: 'Completed', defaultValue: false }),
},
});
export default defineStack({
objects: [TaskObject],
apps: [
App.create({
name: 'task_app',
label: 'Task Management',
icon: 'check-square',
navigation: [/* ... */]
})
],
manifest: {
id: 'com.example.msw-todo',
version: '1.0.0',
type: 'app',
name: 'Task Management',
description: 'MSW + React CRUD Example',
},
});Create a mock setup file (e.g., src/mocks/browser.ts) that initializes the Kernel with the MSW plugin.
// src/mocks/browser.ts
import { ObjectKernel, DriverPlugin, AppPlugin } from '@objectstack/runtime';
import { ObjectQLPlugin } from '@objectstack/objectql';
import { InMemoryDriver } from '@objectstack/driver-memory';
import { MSWPlugin } from '@objectstack/plugin-msw';
import myConfig from '../../objectstack.config'; // Your config
export async function startMockServer() {
// 1. Initialize In-Memory Database
const driver = new InMemoryDriver();
// 2. Create the Kernel
const kernel = new ObjectKernel();
kernel
.use(new ObjectQLPlugin()) // Data Engine
.use(new DriverPlugin(driver, 'memory')) // Database Driver
.use(new AppPlugin(myConfig)) // Load your Schema
.use(new MSWPlugin({ // Expose API via Service Worker
enableBrowser: true,
baseUrl: '/api/v1'
}));
// 3. Boot
await kernel.bootstrap();
// 4. (Optional) Load Initial Data
if (myConfig.manifest?.data) {
// ... logic to seed data into driver ...
}
}Update your entry file (src/main.tsx) to start the mock server before rendering the React app.
// src/main.tsx
import ReactDOM from 'react-dom/client';
import { App } from './App';
import { startMockServer } from './mocks/browser';
async function bootstrap() {
// Wait for Service Worker to be ready
await startMockServer();
ReactDOM.createRoot(document.getElementById('root')!).render(<App />);
}
bootstrap();In your React components, connect to the mock API (which mimics the real backend URL structure).
// src/App.tsx
import { ObjectStackClient } from '@objectstack/client';
// The client thinks it's talking to a real server
const client = new ObjectStackClient({
baseUrl: '' // Relative path, intercepted by MSW at /api/v1/...
});
await client.connect();
const tasks = await client.data.find('task');If you are using Vite, you may need to optimize dependencies to handle CommonJS packages correctly in the browser.
// vite.config.ts
export default defineConfig({
optimizeDeps: {
include: [
'@objectstack/spec',
'@objectstack/spec/data',
'@objectstack/spec/system'
]
}
});# Install dependencies
pnpm install
# Initialize MSW public script (only needed once)
pnpm dlx msw init public/ --save
# Start the dev server
pnpm devOpen http://localhost:3000. You can now create, edit, and delete tasks. The data persists in the browser memory as long as you don't refresh (simulate persistence by seeding data in step 2).
When you are ready to go to production:
- Keep
objectstack.config.ts. - Deploy the official ObjectStack Server (Node.js).
- Point your
ObjectStackClientbaseUrlto the real server. - Remove the
startMockServer()call inmain.tsx.
No frontend code needs to change!