Requirements

  • Node.js 24.0.0 or higher
  • npm 11.0.0 or higher
  • Docker (optional, for image builds and production 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

Built-In Runtime Plugins

For local development and simple single-container services, BSB includes defaults from @bsb/base:

  • config-default: reads sec-config.yaml.
  • config-env: reads the full runtime config from BSB_CONFIG_JSON.
  • events-default: in-process event bus for one container/process.
  • observable-default: console logging plus basic tracing/metrics hooks.

Replace these in production when you need remote config, distributed events, or external observability. Find replacement plugins in the Plugin Registry.

Next Steps

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

Extend BSB Deploy BSB