BoardKitExamples
Full-Stack Example
Complete backend and frontend working together
This example shows how to wire up the NestJS backend and React frontend as a complete application.
Architecture
React App (port 5173)
├── HTTP requests → NestJS API (port 3000)
└── WebSocket → NestJS Gateway (port 3000/board)
NestJS API (port 3000)
├── Prisma → PostgreSQL
├── Local filesystem → ./uploads/
└── In-memory event logBackend: app.module.ts
import { Module } from '@nestjs/common';
import { BoardModule, InMemoryEventLogStorage } from '@hfu.digital/boardkit-nestjs';
import { PrismaBoardAdapter } from '@hfu.digital/boardkit-nestjs';
import { PrismaService } from './prisma.service';
import { JwtAuthGuard } from './auth/jwt-auth.guard';
import { LocalAssetStorage } from './assets/local-asset-storage';
const prisma = new PrismaService();
@Module({
imports: [
BoardModule.register({
storage: new PrismaBoardAdapter({
board: prisma.board,
page: prisma.page,
element: prisma.element,
boardMember: prisma.boardMember,
shareLink: prisma.shareLink,
}),
assetStorage: new LocalAssetStorage('./uploads'),
eventLogStorage: new InMemoryEventLogStorage(),
authGuard: new JwtAuthGuard(process.env.JWT_SECRET ?? 'dev-secret'),
}),
],
})
export class AppModule {}Backend: main.ts
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
// Enable CORS for the React dev server
app.enableCors({
origin: 'http://localhost:5173',
credentials: true,
});
await app.listen(3000);
console.log('API: http://localhost:3000');
console.log('WebSocket: ws://localhost:3000/board');
}
bootstrap();Frontend: main.tsx
import { createRoot } from 'react-dom/client';
import { BoardKitProvider } from '@hfu.digital/boardkit-react';
import { Whiteboard } from './Whiteboard';
const token = 'your-jwt-token'; // from your auth flow
createRoot(document.getElementById('root')!).render(
<BoardKitProvider
config={{
apiUrl: 'http://localhost:3000',
wsUrl: 'ws://localhost:3000/board',
authToken: token,
}}
>
<Whiteboard boardId="my-board-id" />
</BoardKitProvider>,
);Frontend: Whiteboard.tsx
import {
BoardCanvas,
Toolbar,
useBoard,
useCollaboration,
useTool,
useKeyboardShortcuts,
} from '@hfu.digital/boardkit-react';
export function Whiteboard({ boardId }: { boardId: string }) {
const { board, loading, error } = useBoard(boardId);
const { connectionState } = useCollaboration(boardId);
const { activeTool, setTool } = useTool();
useKeyboardShortcuts();
if (loading) return <p>Loading...</p>;
if (error) return <p>Error: {error}</p>;
return (
<div style={{ width: '100vw', height: '100vh' }}>
<header style={{ padding: 8, borderBottom: '1px solid #ccc', display: 'flex', gap: 16 }}>
<strong>{board?.name}</strong>
<span>Connection: {connectionState}</span>
</header>
<Toolbar activeTool={activeTool} onToolChange={setTool} />
<BoardCanvas />
</div>
);
}Database Setup
1. Initialize Prisma
bunx prisma init2. Add the BoardKit schema
Copy the schema from the Prisma Adapter docs into your prisma/schema.prisma.
3. Run migrations
bunx prisma migrate dev --name initRunning the Full Stack
# Terminal 1: Backend
cd backend && bun run start:dev
# Terminal 2: Frontend
cd frontend && bun run devCreating a Board (via API)
curl -X POST http://localhost:3000/boards \
-H "Authorization: Bearer your-jwt-token" \
-H "Content-Type: application/json" \
-d '{"name": "My Whiteboard", "ownerId": "user-1"}'Use the returned board ID in your frontend's boardId prop.
Testing with In-Memory Storage
For quick local development without a database:
import {
BoardModule,
InMemoryBoardStorage,
InMemoryAssetStorage,
InMemoryEventLogStorage,
MockAuthGuard,
} from '@hfu.digital/boardkit-nestjs';
@Module({
imports: [
BoardModule.register({
storage: new InMemoryBoardStorage(),
assetStorage: new InMemoryAssetStorage(),
eventLogStorage: new InMemoryEventLogStorage(),
authGuard: new MockAuthGuard(),
}),
],
})
export class AppModule {}This setup requires no database and accepts any authentication token — ideal for prototyping.