Requirements

  • Node.js 23.0.0 or higher
  • npm 11.0.0 or higher
  • Docker (for container deployment)

Quick Start

The fastest way to get started is to install BSB and create a service plugin.

1

Create a new project

mkdir my-bsb-service
cd my-bsb-service
npm init -y
npm install @bettercorp/service-base typescript @types/node
npx tsc --init
2

Create your service plugin

Create a plugin directory structure:

mkdir -p src/plugins/service-hello
src/plugins/service-hello/index.ts
import { BSBService, BSBServiceConstructor } from "@bettercorp/service-base";
import { z } from "zod";
import { createReturnableEvent, createFireAndForgetEvent } from "@bettercorp/service-base";

// Define your event schemas
export const EventSchemas = {
  emitEvents: {},
  onEvents: {},
  emitReturnableEvents: {},
  onReturnableEvents: {
    'hello.greet': createReturnableEvent(
      z.object({
        name: z.string()
      }),
      z.object({
        message: z.string(),
        timestamp: z.string()
      }),
      'Greet a user by name'
    )
  },
  emitBroadcast: {},
  onBroadcast: {}
} as const;

export class Plugin extends BSBService<null, typeof EventSchemas> {
  public initBeforePlugins?: string[];
  public initAfterPlugins?: string[];
  public runBeforePlugins?: string[];
  public runAfterPlugins?: string[];

  constructor(config: BSBServiceConstructor<null, typeof EventSchemas>) {
    super({
      ...config,
      eventSchemas: EventSchemas
    });
  }

  public async init() {
    // Register event handlers
    await this.events.onReturnableEvent("hello.greet", async (trace, input) => {
      this.log.info(trace, "Greeting {name}", { name: input.name });

      return {
        message: `Hello, ${input.name}!`,
        timestamp: new Date().toISOString()
      };
    });
  }

  public async run() {
    this.log.info(this.metrics.createTrace("run").trace, "Hello service is running");
  }

  public dispose() {
    // Cleanup if needed
  }
}
3

Create the configuration

sec-config.yaml
plugins:
  service-hello:
    enabled: true
4

Run your service

# Development mode (with hot reload)
npx ts-node src/dev.ts

# Or production mode
npm run build
node lib/cli.js

Understanding the Structure

Plugin Directory

BSB discovers plugins by scanning the src/plugins/ directory. Each plugin must:

  • Be in a folder named service-*, events-*, logging-*, config-*, or metrics-*
  • Export a Plugin class that extends the appropriate base class
  • Optionally export a Config class for plugin configuration

Event Schemas

The EventSchemas object defines your service's event contract:

  • emitEvents - Events you emit (fire-and-forget, first listener)
  • onEvents - Events you listen to (fire-and-forget)
  • emitReturnableEvents - Events you emit and expect a response
  • onReturnableEvents - Events you handle and return a response
  • emitBroadcast - Events you broadcast (all listeners)
  • onBroadcast - Broadcasts you listen to

Working with Events

Emitting Events

// Fire-and-forget (first listener receives)
await this.events.emitEvent("user.created", trace, {
  userId: "123",
  email: "user@example.com"
});

// Request-response (wait for result)
const result = await this.events.emitEventAndReturn(
  "user.validate",
  trace,
  { email: "user@example.com" },
  5 // timeout in seconds
);

// Broadcast (all listeners receive)
await this.events.emitBroadcast("cache.invalidate", trace, {
  keys: ["user:123"]
});

Handling Events

// Handle fire-and-forget
await this.events.onEvent("user.created", trace, async (t, input) => {
  this.log.info(t, "User created: {userId}", { userId: input.userId });
});

// Handle request-response
await this.events.onReturnableEvent("user.validate", trace, async (t, input) => {
  const isValid = await validateEmail(input.email);
  return { valid: isValid };
});

// Handle broadcast
await this.events.onBroadcast("cache.invalidate", trace, async (t, input) => {
  for (const key of input.keys) {
    await cache.delete(key);
  }
});

Adding Configuration

To add configuration to your plugin, create a Config class:

import { BSBPluginConfig } from "@bettercorp/service-base";
import { z } from "zod";

const configSchema = z.object({
  greeting: z.string().default("Hello"),
  maxNameLength: z.number().default(100)
});

export class Config extends BSBPluginConfig<typeof configSchema> {
  validationSchema = configSchema;
}

export class Plugin extends BSBService<Config, typeof EventSchemas> {
  // Now this.config is typed with your schema
  async init() {
    const greeting = this.config.greeting; // "Hello"
    const max = this.config.maxNameLength; // 100
  }
}

Then in your sec-config.yaml:

plugins:
  service-hello:
    enabled: true
    greeting: "Welcome"
    maxNameLength: 50

Next Steps

Now that you've built your first service, explore more advanced topics.

Extend BSB Core Plugins