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 Zod, automatic validation ✅ 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: Zod validation 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: Zod 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 (Zod)
↓
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 Zod 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 Zod 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 Zod
- 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:
Tree view shows nested config fields and object branches.
storage
storage
object
path
storage.path
string
default: "./.temp/demo-todos.json"
autoSaveInterval
storage.autoSaveInterval
number
default: 5000
prettyPrint
storage.prettyPrint
boolean
default: true
http
http
object
port
http.port
number
default: 3000
host
http.host
string
default: "0.0.0.0"
cors
http.cors
boolean
default: true
features
features
object
statsInterval
features.statsInterval
number
default: 30
maxTodos
features.maxTodos
number
default: 1000
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
title
string
The title of the todo item
description
string
optional
The description of the todo item
id
uuid
The unique identifier of the todo item
title
string
The title of the todo item
description
string
optional
The description of the todo item
completed
boolean
Whether the todo item is completed
createdAt
datetime
The date and time the todo item was created
updatedAt
datetime
The date and time the todo item was last updated
todo.get
returnable
Get a todo by ID
todo.get
returnable
id
uuid
The unique identifier of the todo item
id
uuid
The unique identifier of the todo item
title
string
The title of the todo item
description
string
optional
The description of the todo item
completed
boolean
Whether the todo item is completed
createdAt
datetime
The date and time the todo item was created
updatedAt
datetime
The date and time the todo item was last updated
todo.list
returnable
List all todos
todo.list
returnable
todos
object[]
The list of todo items
total
int32
The total number of todo items
todo.update
returnable
Update a todo item
todo.update
returnable
id
uuid
The unique identifier of the todo item
title
string
optional
The title of the todo item
description
string
optional
The description of the todo item
completed
boolean
optional
Whether the todo item is completed
id
uuid
The unique identifier of the todo item
title
string
The title of the todo item
description
string
optional
The description of the todo item
completed
boolean
Whether the todo item is completed
createdAt
datetime
The date and time the todo item was created
updatedAt
datetime
The date and time the todo item was last updated
todo.delete
returnable
Delete a todo item
todo.delete
returnable
id
uuid
The unique identifier of the todo item
success
boolean
Whether the deletion was successful
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
id
uuid
The unique identifier of the todo item
title
string
The title of the todo item
description
string
optional
The description of the todo item
completed
boolean
Whether the todo item is completed
createdAt
datetime
The date and time the todo item was created
updatedAt
datetime
The date and time the todo item was last updated
todo.updated
fire-and-forget
Emitted when a todo is updated
todo.updated
fire-and-forget
id
uuid
The unique identifier of the todo item
title
string
The title of the todo item
description
string
optional
The description of the todo item
completed
boolean
Whether the todo item is completed
createdAt
datetime
The date and time the todo item was created
updatedAt
datetime
The date and time the todo item was last updated
todo.deleted
fire-and-forget
Emitted when a todo is deleted
todo.deleted
fire-and-forget
id
uuid
Todo ID
Listen to Broadcasts
Broadcast events this plugin subscribes to.
todo.stats
broadcast
Broadcast todo statistics
todo.stats
broadcast
total
int32
Total number of todos
completed
int32
Number of completed todos
pending
int32
Number of pending todos
timestamp
datetime
Timestamp when stats were generated
Available Versions
Dependencies
No dependencies