Demo Todo App
service-demo-todoDemo Todo Service showcasing BSB v9 best practices with cross-language event support
BSB Demo Todo App
A comprehensive demonstration application that showcases Best Service Base (BSB) best practices through a fully-functional todo application with CRUD operations, file-based storage, HTTP server, and responsive web interface.
Overview
The Demo Todo App is designed as a reference implementation for BSB developers, demonstrating key patterns and practices in a real-world application context. It combines multiple BSB concepts into a single, cohesive plugin that's easy to understand and use as a learning resource.
What Makes This Demo Valuable
- Complete Example: Shows how all BSB components work together in a real application
- Best Practices: Follows established patterns from core BSB plugins
- Self-Contained: Works out-of-the-box with zero external dependencies (except BSB core)
- Interactive: Provides a visual web interface to see BSB events in action
- Educational: Well-commented code suitable for learning
Key Features
✅ Schema-First Events - All events defined with BSB types and exported contracts ✅ File-Based Storage - Persistent JSON storage following BSB patterns ✅ HTTP REST API - Simple server with proper routing and CORS ✅ Web Interface - Responsive single-page application ✅ Observable Integration - Full logging, metrics, and tracing ✅ Real-Time Updates - Live statistics and event logging ✅ Production-Ready Patterns - Proper lifecycle, error handling, cleanup
BSB Patterns Demonstrated
This demo showcases 10 critical BSB patterns:
- Schema-First Events: BSB type schemas for all event inputs/outputs
- Client Pattern: Storage client created in constructor
- Resource Lifecycle: Proper init/run/dispose with cleanup
- Observable Integration: Logging, metrics, tracing throughout
- Configuration: AnyVali schema with sensible defaults
- File Storage: Following observable-logging-file pattern
- Event Types: Fire-and-forget, returnable, and broadcast events
- Metrics: Counters and histograms for monitoring
- Distributed Tracing: Spans for HTTP requests and operations
- Resource Context: Automatic from plugin configuration
Installation
Prerequisites
Ensure you have the following BSB plugins enabled:
observable-default(or another Observable implementation)events-default(or another Events implementation)
Install the Plugin
npm install @bsb/demo-todo-app
Or install from source:
cd plugins/nodejs/demo-todo-app
npm install
npm run build
Configuration
Add to your BSB sec-config.yaml:
default:
# Required dependencies
observable:
observable-default:
plugin: observable-default
enabled: true
events:
events-default:
plugin: events-default
enabled: true
# Demo Todo App
services:
service-demo-todo:
plugin: service-demo-todo
enabled: true
config:
storage:
path: "./.temp/demo-todos.json"
autoSaveInterval: 5000
prettyPrint: true
http:
port: 3000
host: "0.0.0.0"
cors: true
features:
statsInterval: 30
maxTodos: 1000
Configuration Options
Storage Settings
path(string, default:"./.temp/demo-todos.json")- Path to JSON storage file (relative to working directory)
- Directory will be created automatically if it doesn't exist
autoSaveInterval(number, default:5000)- Auto-save interval in milliseconds
- Minimum: 1000ms (1 second)
prettyPrint(boolean, default:true)- Format JSON with indentation for readability
- Set to
falsefor compact storage
HTTP Settings
port(number, default:3000)- HTTP server port (1-65535)
- Change if port is already in use
host(string, default:"0.0.0.0")- HTTP server bind address
- Use
"127.0.0.1"for localhost-only access
cors(boolean, default:true)- Enable CORS headers for cross-origin requests
- Useful for development and API testing
Feature Settings
statsInterval(number, default:30)- Broadcast statistics every N seconds
- Set to
0to disable periodic broadcasts
maxTodos(number, default:1000)- Maximum number of todos allowed
- Prevents excessive storage usage
Usage
Starting the Application
bsb start
The application will:
- Initialize storage and load existing todos
- Register event handlers
- Start the HTTP server
- Begin broadcasting statistics (if enabled)
Accessing the Web Interface
Open your browser to: http://localhost:3000
The web interface provides:
- Real-time statistics (total, pending, completed)
- Add new todos with title and optional description
- Filter todos by status (all, pending, completed)
- Toggle completion status
- Delete todos
- Live event log showing BSB events
Using the REST API
List All Todos
curl http://localhost:3000/api/todos
Response:
{
"todos": [
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"title": "Learn BSB",
"description": "Study the demo app",
"completed": false,
"createdAt": "2025-01-15T10:30:00.000Z",
"updatedAt": "2025-01-15T10:30:00.000Z"
}
],
"total": 1
}
Create a Todo
curl -X POST http://localhost:3000/api/todos \
-H "Content-Type: application/json" \
-d '{"title": "Learn BSB", "description": "Study the demo app"}'
Response:
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"title": "Learn BSB",
"description": "Study the demo app",
"completed": false,
"createdAt": "2025-01-15T10:30:00.000Z",
"updatedAt": "2025-01-15T10:30:00.000Z"
}
Get a Specific Todo
curl http://localhost:3000/api/todos/550e8400-e29b-41d4-a716-446655440000
Update a Todo
curl -X PATCH http://localhost:3000/api/todos/550e8400-e29b-41d4-a716-446655440000 \
-H "Content-Type: application/json" \
-d '{"completed": true}'
You can update:
title(string, 1-200 characters)description(string, 0-1000 characters)completed(boolean)
Delete a Todo
curl -X DELETE http://localhost:3000/api/todos/550e8400-e29b-41d4-a716-446655440000
Response:
{
"success": true
}
Events API
The demo app exposes several events that other BSB services can interact with.
Returnable Events (CRUD Operations)
todo.create
Create a new todo item.
Input:
{
title: string, // 1-200 characters, required
description?: string // 0-1000 characters, optional
}
Output: TodoItem
Example:
const todo = await emitEventAndReturn('todo.create', obs, {
title: 'Complete project',
description: 'Finish all remaining tasks'
});
todo.get
Get a todo by ID.
Input:
{
id: string // UUID format
}
Output: TodoItem
todo.list
List all todos.
Input: {}
Output:
{
todos: TodoItem[],
total: number
}
todo.update
Update a todo item.
Input:
{
id: string, // UUID format, required
title?: string, // 1-200 characters
description?: string, // 0-1000 characters
completed?: boolean
}
Output: TodoItem (updated)
todo.delete
Delete a todo item.
Input:
{
id: string // UUID format
}
Output:
{
success: boolean
}
Fire-and-Forget Events (Notifications)
These events are emitted automatically when CRUD operations occur:
todo.created- Emitted when a todo is created- Payload:
TodoItem
- Payload:
todo.updated- Emitted when a todo is updated- Payload:
TodoItem
- Payload:
todo.deleted- Emitted when a todo is deleted- Payload:
{ id: string }
- Payload:
Example - Listening to notifications:
onEvent('todo.created', async (todo, obs) => {
obs.log.info(`New todo created: ${todo.title}`);
// Send notification, update cache, etc.
});
Broadcast Events
todo.stats
Periodic broadcast of todo statistics (if statsInterval > 0).
Payload:
{
total: number,
completed: number,
pending: number,
timestamp: string // ISO datetime
}
Example - Listening to broadcasts:
onBroadcast('todo.stats', async (stats, obs) => {
obs.log.info(`Current stats: ${stats.pending} pending, ${stats.completed} completed`);
});
Architecture
Component Overview
┌─────────────────────────────────────────────────────┐
│ HTTP Requests │
└──────────────────┬──────────────────────────────────┘
│
┌──────▼───────┐
│ HTTP Server │
│ (Port 3000) │
└──────┬───────┘
│
┌─────────▼──────────┐
│ API Handler │
│ (Event Emitter) │
└─────────┬──────────┘
│
┌──────▼───────┐
│ Event System │
│ (BSB Core) │
└──────┬───────┘
│
┌─────────▼──────────┐
│ Event Handlers │
│ (CRUD Operations) │
└─────────┬──────────┘
│
┌─────────▼──────────┐
│ Storage Client │
│ (Map + File I/O) │
└─────────┬──────────┘
│
┌──────▼───────┐
│ File System │
│ (.temp/...) │
└──────────────┘
Data Flow
Create Todo Flow
1. POST /api/todos
↓
2. HTTP Server receives request
↓
3. API Handler parses body
↓
4. emitEventAndReturn('todo.create', { title, description })
↓
5. Event handler validates input against the event schema
↓
6. Storage.create() adds to map, marks dirty
↓
7. emitEvent('todo.created', todo)
↓
8. Returns todo to API Handler
↓
9. HTTP Server sends JSON response
↓
10. Auto-save writes to disk (within 5s)
Storage Strategy
The storage client uses a two-tier approach:
- In-Memory Map: Fast read/write operations
- File Persistence: Periodic auto-save + final save on dispose
This provides:
- ⚡ Fast performance for frequent operations
- 💾 Durability through persistent storage
- 🔄 Automatic recovery on restart
- 🛡️ Data safety with final save on shutdown
Metrics
The demo app exposes metrics that can be monitored:
Counters
demo_todo_total- Total number of todos
- Increments on create, decrements on delete
demo_http_requests_total- Total HTTP requests received
- Labels:
method,path
Histograms
demo_http_request_duration_ms- HTTP request duration in milliseconds
- Buckets: [10, 50, 100, 500, 1000]
- Labels:
method,path
Best Practices Reference
1. Constructor Pattern
constructor(config: BSBServiceConstructor) {
super({ ...config, eventSchemas: EventSchemas });
// Create lightweight clients in constructor
this.storage = new TodoStorage(this.cwd, this.config.storage, logger);
}
Why: Constructors should be fast and synchronous. Heavy I/O happens in init().
2. Init Pattern
async init(obs: Observable): Promise<void> {
// Heavy I/O operations
await this.storage.init();
// Create metrics
this.todoCounter = obs.metrics.counter(...);
// Register event handlers
await this.registerEventHandlers(obs);
}
Why: Separates initialization from construction, provides Observable context.
3. Event Handler Pattern
this.onReturnableEvent('todo.create', async (data, obs) => {
// Input is automatically validated by the event schema
obs.log.info(`Creating todo: ${data.title}`);
// Perform operation
const todo = this.storage.create(data.title, data.description);
// Emit notification event
await this.emitEvent('todo.created', obs, todo);
// Return is automatically validated by the event schema
return todo;
});
Why: Validation is automatic, Observable is available, tracing is built-in.
4. Dispose Pattern
dispose(): void {
// Stop timers
if (this.statsTimer) clearInterval(this.statsTimer);
// Stop servers
if (this.httpServer) this.httpServer.stop();
// Cleanup clients (final save happens here)
if (this.storage) this.storage.dispose();
}
Why: Ensures clean shutdown, prevents resource leaks, saves pending data.
5. Storage Pattern (from TodoStorage)
class TodoStorage {
private todos: Map<string, TodoItem> = new Map();
private isDirty = false;
private autoSaveTimer?: NodeJS.Timeout;
async init() {
// Load from disk
// Start auto-save timer
}
create(title: string, description?: string): TodoItem {
// Add to map
this.isDirty = true;
return todo;
}
dispose() {
// Stop timer
// Final save
}
}
Why: Fast in-memory operations + persistent storage + automatic cleanup.
Troubleshooting
Port Already in Use
Problem: Error: listen EADDRINUSE: address already in use :::3000
Solution: Change the port in your configuration:
services:
service-demo-todo:
config:
http:
port: 3001
Storage File Not Created
Problem: Todos don't persist after restart
Solutions:
- Check that the directory is writable
- Verify
storage.pathin configuration - Check logs for file system errors
- Ensure proper shutdown (allows final save)
Events Not Working
Problem: Event handlers not firing
Solutions:
- Ensure
events-defaultplugin is enabled - Ensure
observable-defaultplugin is enabled - Check BSB logs for initialization errors
- Verify plugin load order in configuration
CORS Errors
Problem: Browser blocks API requests from different origin
Solution: Enable CORS in configuration (default: enabled):
services:
service-demo-todo:
config:
http:
cors: true
Maximum Todos Reached
Problem: Cannot create new todos
Solution: Increase or remove the limit:
services:
service-demo-todo:
config:
features:
maxTodos: 10000 # Or any higher number
Learning Path
For New BSB Developers
Start Here: Read
src/plugins/service-demo-todo/index.ts- Understand event schema definitions
- See how configuration works
- Study the lifecycle methods
Study Storage: Read
src/plugins/service-demo-todo/storage.ts- Learn the file storage pattern
- Understand init/dispose lifecycle
- See auto-save implementation
Explore HTTP: Read
src/plugins/service-demo-todo/http-server.ts- See how to integrate Observable with HTTP
- Learn routing and static file serving
- Study security considerations
Run It: Start the app and use the web interface
- Watch the event log
- Inspect the storage file
- Check BSB logs for observability
For Experienced Developers
Use this as a reference for:
- Schema-first API design with BSB types and AnyVali config
- Event-driven architecture patterns
- Observable integration strategies
- File-based persistence patterns
- HTTP server implementation
- Client lifecycle management
Support
- Documentation: See README.md for detailed usage
- Issues: Report bugs or request features on GitHub
- Examples: Check
static/app.jsfor frontend integration examples
License
(AGPL-3.0-only OR Commercial)
Installation
bsb client install service-demo-todo
bsb plugin install service-demo-todo
npm install @bsb/demo-todo-app
Configuration
Configuration options for this plugin:
No configuration required
Events
Events available to clients of this plugin:
Emit and Return
Call these events and receive a response.
todo.create
returnable
Create a new todo item
todo.create
returnable
todo.get
returnable
Get a todo by ID
todo.get
returnable
todo.list
returnable
List all todos
todo.list
returnable
todo.update
returnable
Update a todo item
todo.update
returnable
todo.delete
returnable
Delete a todo item
todo.delete
returnable
Listen (Fire and Forget)
Events this plugin handles silently.
todo.created
fire-and-forget
Emitted when a todo is created
todo.created
fire-and-forget
todo.updated
fire-and-forget
Emitted when a todo is updated
todo.updated
fire-and-forget
todo.deleted
fire-and-forget
Emitted when a todo is deleted
todo.deleted
fire-and-forget
Listen to Broadcasts
Broadcast events this plugin subscribes to.
todo.stats
broadcast
Broadcast todo statistics
todo.stats
broadcast
Available Versions
Dependencies
No dependencies