Skip to content

Router

Router is the local pool-router — the routees are children of the router actor, created when the router is spawned, supervised by it.

The API has four factories:

Router.roundRobin(size, routeeProps)
Router.random(size, routeeProps)
Router.broadcast(size, routeeProps)
Router.custom(size, routeeProps, strategy)

Each returns a Props<TMsg | Broadcast<TMsg>> — pass it to system.actorOf or context.actorOf like any other Props. The type parameter from routeeProps flows through, so the resulting ref is typed.

import { ActorSystem, Props, Router, Actor, Broadcast } from 'actor-ts';
type Msg = { payload: string };
class Worker extends Actor<Msg> {
override onReceive(msg: Msg): void {
this.log.info(`processed ${msg.payload}`);
}
}
const system = ActorSystem.create('demo');
const pool = system.actorOf(
Router.roundRobin(4, Props.create(() => new Worker())),
'workers',
);
// One message per routee, cycling:
pool.tell({ payload: 'a' }); // → routee-1
pool.tell({ payload: 'b' }); // → routee-2
pool.tell({ payload: 'c' }); // → routee-3
pool.tell({ payload: 'd' }); // → routee-4
// Override the strategy for a single message — send to ALL routees:
pool.tell(new Broadcast({ payload: 'announce' }));

The pool’s path is actor-ts://demo/user/workers; the routees are actor-ts://demo/user/workers/routee-1 through routee-4.

When you actorOf a router Props, the runtime:

  1. Creates one RouterActor instance. It’s the actor with the path you provided ('workers' in the example).
  2. Inside RouterActor.preStart, it spawns size children using routeeProps, named routee-1 through routee-N.
  3. It watches every routee so it can react if one stops.

The router is now ready. Any tell to the router ref runs the strategy and forwards.

Broadcast — override the strategy per-message

Section titled “Broadcast — override the strategy per-message”
import { Broadcast } from 'actor-ts';
pool.tell({ payload: 'a' }); // normal: one routee
pool.tell(new Broadcast({ payload: 'announce' })); // every routee

Broadcast<T> wraps a payload. The router unwraps it, ignores the strategy, and sends the inner message to every routee. Useful for occasional fan-out messages (cache invalidation, schema update notifications) that don’t fit the routine routing pattern.

The router accepts both TMsg and Broadcast<TMsg> — the type parameter on the returned Props reflects that.

import { Router, type RoutingStrategy } from 'actor-ts';
// Send to the FIRST routee for the first 100 messages, then round-robin.
const warmupStrategy: RoutingStrategy = (routees, state) => {
if (state.messageIndex < 100) return [routees[0]];
return [routees[state.messageIndex % routees.length]];
};
const pool = system.actorOf(
Router.custom(4, Props.create(() => new Worker()), warmupStrategy),
'workers',
);

A RoutingStrategy is a function from (routees, state) to an Iterable<ActorRef> — return one ref for single-target routing, multiple for fan-out. Empty iterable means “drop this message” (silent, no dead-letter routing — your responsibility to log if needed).

See Strategies for the full strategy type and built-in implementations.

Router.roundRobin(size, props) is a pool — the router creates routees from props. If you want to route to existing actors instead (e.g. shard regions, specific named workers), the local Router doesn’t support that; you’d write a custom router actor.

For cluster setups, ClusterRouter does have a “find existing actors by path” mode — see Pool vs group for the distinction and Cluster router for the cluster API.

pool.stop() (or pool.tell(PoisonPill.instance)) stops the router, which cascade-stops every routee. Or stop a single routee by addressing it directly:

const oneRoutee = (await system.actorSelection(
'/user/workers/routee-2'
).resolveOne()) as ActorRef<Msg>;
oneRoutee.stop();

When a routee stops, the router watches it and… does nothing by default in the framework’s current router. The pool size effectively shrinks until restart. For self-healing pools, wrap the routee Props in a supervisor strategy that restarts on Stop (or wrap the whole pool in a BackoffSupervisor).

  • Strategies — round-robin, random, broadcast, custom; plus the cluster-only consistent-hashing.
  • Pool vs group — when you want existing routees instead of pool-spawned ones.
  • Cluster router — the membership-driven cluster equivalent.
  • Props — the routee Props shape; withSupervisorStrategy, withDispatcher, etc., all apply to routees individually.

The Router and Broadcast API references cover the full surface.