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 @bsb/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,
  Observable,
  bsb,
  createConfigSchema,
  createEventSchemas,
  createReturnableEvent,
} from "@bsb/base";
import * as av from "anyvali";

const Config = createConfigSchema(
  {
    name: "service-hello",
    description: "Hello service plugin",
    tags: ["service", "example"],
    documentation: ["./docs/service-hello.md"],
  },
  av.object({}),
);

const EventSchemas = createEventSchemas({
  emitEvents: {},
  onEvents: {},
  emitReturnableEvents: {},
  onReturnableEvents: {
    "hello.greet": createReturnableEvent(
      bsb.object({ name: bsb.string() }, "Greeting input"),
      bsb.object({
        message: bsb.string(),
        timestamp: bsb.datetime(),
      }, "Greeting response"),
      "Greet a user by name",
    ),
  },
  emitBroadcast: {},
  onBroadcast: {},
});

export class Plugin extends BSBService<InstanceType<typeof Config>, typeof EventSchemas> {
  static Config = Config;
  static EventSchemas = EventSchemas;

  constructor(cfg: BSBServiceConstructor<InstanceType<typeof Config>>) {
    super(cfg);
  }

  async init(obs: Observable) {
    obs.log.info("Initializing {plugin}", { plugin: this.pluginName });

    await this.events.onReturnableEvent("hello.greet", obs, async (handlerObs, input) => {
      handlerObs.log.info("Greeting {name}", { name: input.name });
      return {
        message: `Hello, ${input.name}!`,
        timestamp: new Date().toISOString(),
      };
    });
  }

  async run(obs: Observable) {
    obs.log.info("Hello service is running");
  }

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

Create the configuration

sec-config.yaml
default: # deployment profile
  observable:
    observable-default:
      plugin: observable-default
      enabled: true
      config: {}
  events:
    events-default:
      plugin: events-default
      enabled: true
  services:
    service-hello:
      plugin: service-hello
      enabled: true
      config: {}
4

Run your service

# Development mode (TypeScript plugins, hot reload)
npm run dev

# Or production mode (compiled JavaScript only)
npm run build
npm start

Understanding the Structure

Plugin Directory

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

  • Be in a folder named service-*, events-*, observable-*, or config-*
  • 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

Each event name must appear in only one EventSchemas category. Do not declare the same key in emitEvents, onEvents, emitReturnableEvents, onReturnableEvents, emitBroadcast, or onBroadcast. If a plugin emits an event and also needs to listen to that service contract, instantiate the generated service client and register the listener through the client onX API instead of adding a matching on* entry to the same plugin.

Working with Events

Emitting Events

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

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

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

Handling Events

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

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

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

Adding Configuration

To add configuration to your plugin, create a config schema with AnyVali. BSB strips unknown config keys centrally during startup, so plugin schemas do not need { unknownKeys: "strip" }.

import * as av from "anyvali";
import { createConfigSchema } from "@bsb/base";

const Config = createConfigSchema(
  {
    name: "service-hello",
    description: "Hello service",
    tags: ["hello", "example"]
  },
  av.object({
    greeting: av.string().default("Hello"),
    maxNameLength: av.int32().default(100)
  })
);

export class Plugin extends BSBService<InstanceType<typeof Config>, typeof EventSchemas> {
  static Config = Config;
  static EventSchemas = EventSchemas;

  // Now this.config is typed with your schema
  async init(obs: Observable) {
    const greeting = this.config.greeting; // "Hello"
    const max = this.config.maxNameLength; // 100
  }
}

Then in your sec-config.yaml:

default:
  services:
    service-hello:
      plugin: service-hello
      enabled: true
      config:
        greeting: "Welcome"
        maxNameLength: 50

Next Steps

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

Extend BSB Core Plugins