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.
A minimal example
Section titled “A minimal example”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-1pool.tell({ payload: 'b' }); // → routee-2pool.tell({ payload: 'c' }); // → routee-3pool.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.
What happens at spawn time
Section titled “What happens at spawn time”When you actorOf a router Props, the runtime:
- Creates one
RouterActorinstance. It’s the actor with the path you provided ('workers'in the example). - Inside
RouterActor.preStart, it spawnssizechildren usingrouteeProps, namedroutee-1throughroutee-N. - 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 routeepool.tell(new Broadcast({ payload: 'announce' })); // every routeeBroadcast<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.
Router.custom — bring your own strategy
Section titled “Router.custom — bring your own strategy”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.
Spawning the routees yourself
Section titled “Spawning the routees yourself”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.
Stopping the pool
Section titled “Stopping the pool”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).
Where to next
Section titled “Where to next”- 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.